如何不从方法返回mutable并暴露类内部

时间:2016-08-01 17:38:50

标签: c# immutability

我认为这是一个常见问题,我相信我已经很好地涵盖了这些基础,就像在函数之间传递对象,结构或值类型一样。我们假设我有以下类:

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
    }
}

如何确保User类的内部保持不变。我知道对于这个问题,暴露xCoor和yCoor而不是整个User类也是合适的,但是大部分时间我都面临问题(需要?)来返回对类的引用。我很感激任何建议。

4 个答案:

答案 0 :(得分:1)

您可以采取多种方法。如果在实例化xCoor之后永远不会更改yCoorUser的情况,您可以在其构造函数中要求值并使setter private确保它们在这堂课之外不能改变。

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并访问这些属性。

另一种选择是创建一个包装User对象的新类,并且只公开从实际User实例读取属性的getter,但不能设置那些相同的属性。此选项可以与上面的IReadableUser方法结合使用,以隐藏返回对象的实现细节,同时仍然阻止人们将对象强制转换为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));
}

另一种选择是允许用户改变你返回的值,但确保这些值是复制值,以便下次有人要求他们看到的用户列表时没有改变。

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

如果您的User课程中有更多基于参考的值,那么您的Select语句也需要创建这些值的新实例。如果这变得很麻烦,你可以考虑给每个类一个复制构造函数,以确保复制值的责任被合并到代码中最有可能被注意到并在事情发生变化时被修复的部分。

答案 1 :(得分:0)

我很抱歉将此标记为答案。丢失了我以前的帐户,并且没有足够的声誉将此作为评论发布(尚未)。

执行此操作的一种方法是使所有属性readOnly并通过构造函数设置值。但是,我不确定这是否适合您。如果类的行为修改了对象的状态,那么可能会在方向盘中抛出一个扳手。

关于列表,如果列表返回对象的副本(即返回列表的副本),则应该是好的。如果您希望单个对象不可变,则有2个选项

  1. 制作单个数据容器(对象)的副本,而不是仅仅复制引用(这就是这个)(
  2. 通过将属性公开为仅获取并将设置者设置为私有来强制数据容器不可变。
  3. 希望有所帮助。

答案 2 :(得分:0)

可能会移除这些集合或将其设为私有:

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;}
}

答案 3 :(得分:0)

有几种方法。我怀疑你只需要删除&#34; set&#34;从每一组/得到。

你可以:

{{1}}

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