C#不变性......骗人?

时间:2014-02-27 03:43:00

标签: c# multithreading immutability

在多线程环境中克隆对象(例如:列表)以促进不变性是一种理想的做法。但是,如果我们这样做,它可能是API用户的谎言。我所说的是......

请考虑以下代码:

public class Teacher {

    public List<Student> Students = new List<Student>();

    public Student GetStudent(int index) {
        return Students[index].Clone();
    }
}

public class Student {
    public DateTime LastAttended { get; set; }
}

并且API的用户可以这样做:

var teacher = new Teacher();
var student3 = teacher.GetStudent(3);

student3.LastAttended = DateTime.Now;

如果没有适当的文档,用户就无法知道他所获得的学生对象实际上是一个克隆对象,在这种情况下,对该对象所做的所有更改都不会反映原始对象。

如何改进上述代码,以便用户直观地了解GetStudent仅用于阅读而不是用于修改?有没有办法强制/限制修改从GetStudent方法返回的Student对象?

1 个答案:

答案 0 :(得分:4)

您的Student对象根本不是一成不变的。如果您想要不变性,请创建一个不可变对象:

public sealed class Student {
    private readonly DateTime _lastAttended;
    public DateTime LastAttended { get { return _lastAttended; } }

    public Student(DateTime lastAttended)
    {    
        _lastAttended = lastAttended;
    }
}

如果您不希望某人设置属性的值,则不要公开setter,只显示getter。

这当然需要围绕此构建应用程序。如果您确实需要更新LastAttended时间,那么您可以这样做,例如通过更新数据库并返回新的Student对象的存储库。此外,许多ORM无法自动处理不可变对象并需要一些转换代码。

请注意,当人们在内存中缓存对象然后传递它们时,例如,您的问题是非常常见的。查看操作它们的模型,在不知不觉中修改缓存中的主对象。这就是为什么经常建议克隆缓存的原因。克隆可以保护您免受代码修改“您的”对象 - 每次有人问,他们都会获得主对象的新实例。克隆不会阻止调用者弄乱他自己的版本。

请注意,如果字段的类型是可变类型,则将字段声明为只读也不会有太大作用 - 我仍然可以执行以下操作: Student.Course.Name = "Test";即使课程为readonly - 我无法更改学生对象中的引用,但我可以访问任何属性设置器。

真正的不变性在C#中有点痛苦,因为它有很多打字和很多工厂方法。在某些时候,可能只是留下一个正常的可变获取/设置,并相信呼叫者知道该怎么做,因为他们只能弄乱自己,而不是你。也就是说,任何实际操作数据库中数据的东西都需要进行适当的安全/业务规则检查。