如何不 return 方法中的可变变量并公开 class 内部结构

How not to return a mutable from a method and exposing class internals

我认为这是一个常见问题,我相信我已经很好地涵盖了基础知识,例如对象、结构或值类型如何在函数之间传递。让我们假设我有以下 classes:

public class User{
    public int xCoor{get;set}
    public int yCoor{get;set}
}

public class UserManager{
    Dictionary<int,User> userMapping = new  Dictionary<int,User>();
    public void AddUser(User user){//stuff}
    public void RemoveUser(User user){//stuff}
    public IEnumerable<User> GetAllUsers(){return userMapping.Values;}
}

public class UserConsumer{
    private UserManager;
    public void GetUserCoordinates(){
        var listThatExposesInternals = UserManager.GetAllUsers().ToList();
        listThatExposesInternals.Foreach(user => user.xCoor = user.yCoor = 0);
    // Now that I altered the internals of the class, it is messed up
    // I only want the consumer to be able read
    }
}

如何确保用户 class 的内部结构保持完整。我知道对于这个问题,公开 xCoor 和 yCoor 而不是整个用户 class 也是合适的,但大多数时候我遇到的问题(需要?) return 对 class。我感谢任何建议。

很抱歉将此标记为答案。失去了我以前的帐户并且没有足够的声誉 post 作为评论(还没有)。

一种方法是将所有属性设置为只读并通过构造函数设置值。但是,我不确定这是否适合您。如果 class 的行为修改了对象的状态,那么这可能会造成麻烦。

关于List,如果listreturns对象的副本(即returnsList的副本)那应该不错。如果您希望单个对象不可变,您有 2 个选项

  1. 制作单个数据容器(对象)的副本,而不是仅仅复制引用(这一个所做的)
  2. 通过将属性公开为 get only 并将 setter 设置为私有来强制数据容器不可变。

希望对您有所帮助。

也许删除集或将它们设为私有:

public class User{
    public int xCoor{get;}
    public int yCoor{get;}
}

public class User{
    public int xCoor{get; private set;}
    public int yCoor{get; private set;}
}

您可以采用多种方法。如果 xCooryCoor 在实例化 User 之后永远不会发生更改的情况,您可以在其构造函数中要求值并使 setter private 以确保无法在此 class.

之外更改它们
public class User
{

    public User(int xCoor, int yCoor)
    {
        this.xCoor = xCoor;
        this.yCoor = yCoor;
    }

    public int xCoor{get; private set;}
    public int yCoor{get; private set;}
}

如果您确实需要 User 是可变的,但又不想在某些情况下更改属性,您可以为 User 创建一个接口来实现,该接口仅对这些属性具有 getter。但是,人们仍然可以将他们的 IReadableUser 转换为 User 并访问这些属性。

另一种选择是创建一个新的 class 来包装 User 对象,并且只公开从实际 User 实例读取属性但不能设置这些属性的 getter相同的属性。此选项可以与上面的 IReadableUser 方法结合使用,以隐藏 returned 对象的实现细节,同时仍然防止人们将对象强制转换为 User 以更改其值。

public interface IReadOnlyUser
{
    int xCoor {get;}
    int yCoor {get;}
}

internal class ReadOnlyUser : IReadOnlyUser
{
    private readonly User user;

    public ReadOnlyUser(User user)
    {
        this.user = user;
    }

    public int xCoor{get { return this.user.xCoor; }}
    public int yCoor{get { return this.user.yCoor; }}
}

public IEnumerable<IReadOnlyUser> GetAllUsers()
{
    return userMapping.Values
        .Select(u => (IReadOnlyUser) new ReadOnlyUser(u));
}

另一种选择是 允许 用户更改您 return 的值,但要确保这些值是复制值,以便下次有人要求他们看到的实例未更改的用户列表。

public IEnumerable<User> GetAllUsers()
{
    return userMapping.Values
        .Select(u => new User { xCoor = u.xCoor, yCoor = u.yCoor });
}

如果您的 User class 中有更多基于引用的值,您的 Select 语句也需要创建这些值的新实例。如果这变得很麻烦,您可能会考虑为每个 class 提供一个复制构造函数,以确保将复制值的责任合并到代码的一部分,当事情发生变化时,它最有可能被注意到和修复。

有几种方法。我怀疑您只需要从每个 set/get 中删除 "set"。

你可以:

public class User
{
    protected int _xCoor = 0;
    protected int _yCoor = 0;

    public int xCoor
    {
        get { return _xCoor; }
    }

    public int yCoor
    {
        get { return _yCoor; }
    }
}

在我的示例中,为了更改 _xCoor 和 _yCoor 的值,您必须创建一个继承自用户 class 的 class。