在C#中创建不可变类的最简洁方法是什么?

我发现自己必须创建许多不可变类,我想找到一种方法来做到这一点,没有多余的信息。 我不能使用匿名类型,因为我需要从方法返回这些类。 我想要intellisense支持,所以我不想使用词典,动态或类似的东西。 我还想要有名的属性,它排除了元组。 到目前为止,我尝试过一些模式:

// inherit Tuple. This has the added benefit of giving you Equals() and GetHashCode() public class MyImmutable : Tuple { public MyImmutable(int field1, string field2, bool field3) : base(field1, field2, field3) { } public int Field1 { get { return this.Item1; } } public string Field2 { get { return this.Item2; } } public bool Field3 { get { return this.Item3; } } } /////////////////////////////////////////////////////////////////////////////////// // using a custom SetOnce struct that throws an error if set twice or if read before being set // the nice thing about this approach is that you can skip writing a constructor and // use object initializer syntax. public class MyImmutable { private SetOnce _field1; private SetOnce _field2; private SetOnce _field3; public int Field1 { get { return this._field1.Value; } set { this._field1.Value = value; } public string Field2 { get { return this._field2.Value; } set { this._field2.Value = value; } public bool Field3 { get { return this._field3.Value; } set { this._field3.Value = value; } } /////////////////////////////////////////////////////////////////////////////////// // EDIT: another idea I thought of: create an Immutable type which allows you to // easily expose types with simple get/set properties as immutable public class Immutable { private readonly Dictionary _values; public Immutable(T obj) { // if we are worried about the performance of this reflection, we could always statically cache // the getters as compiled delegates this._values = typeof(T).GetProperties() .Where(pi => pi.CanRead) // Utils.MemberComparer is a static IEqualityComparer that correctly compares // members so that ReflectedType is ignored .ToDictionary(pi => pi, pi => pi.GetValue(obj, null), Utils.MemberComparer); } public TProperty Get(Expression<Func> propertyAccessor) { var prop = (PropertyInfo)((MemberExpression)propertyAccessor.Body).Member; return (TProperty)this._values[prop]; } } // usage public class Mutable { int A { get; set; } } // we could easily write a ToImmutable extension that would give us type inference var immutable = new Immutable(new Mutable { A = 5 }); var a = immutable.Get(m => mA); // obviously, this is less performant than the other suggestions and somewhat clumsier to use. // However, it does make declaring the immutable type quite concise, and has the advantage that we can make // any mutable type immutable /////////////////////////////////////////////////////////////////////////////////// // EDIT: Phil Patterson and others mentioned the following pattern // this seems to be roughly the same # characters as with Tuple, but results in many // more lines and doesn't give you free Equals() and GetHashCode() public class MyImmutable { public MyImmutable(int field1, string field2, bool field3) { Field1 = field1; Field2 = field2; Field3 = field3; } public int Field1 { get; private set; } public string Field2 { get; private set; } public bool Field3 { get; private set; } } 

这些都比创建只读字段的“标准”模式,通过构造函数设置它们,以及通过属性公开它们更加冗长。 但是,这两种方法仍然有很多冗余样板。

有任何想法吗?

您可以使用私有设置器的自动属性

 public class MyImmutable { public MyImmutable(int field1, string field2, bool field3) { Field1 = field1; Field2 = field2; Field3 = field3; } public int Field1 { get; private set; } public string Field2 { get; private set; } public bool Field3 { get; private set; } } 

看看public {get;private set;}属性是否适合您的情况 – 比单独的字段声明更紧凑,语义完全相同。

更新:正如ChaseMedallion评论并在问题中内联,这种方法不提供自动生成的GetHashCodeEquals方法,这与Tuple方法不同。

 class MyImmutable { public int MyProperty {get; private set;} public MyImmutable(int myProperty) { MyProperty = v; } } 

我喜欢Tuple方法,因为它提供了可以在有趣的上下文中安全使用的对象,并提供了很好的名称。 如果我需要创建这种类型的许多类型,我会考虑重新实现Tuple类:

  • 在构造时,预先计算GetHashCode并将其存储为对象的一部分,以避免对集合/字符串进行无限制的检查。 可能是可选的,允许选择通常用作Dictionary键的情况。
  • 隐藏通用名称(即使用EditorBrowsableAttribute protected或只是隐藏其他智能),因此不会混淆2组名称。
  • 考虑在Debug版本/ FxCop规则中强制执行字段类型是不可变的…

旁注:请查看Eric Lippert 关于不可变类型的系列文章 。

当存在相应的可变类时,不可变类最有用,其内容可以很容易地从不可变类型实例加载或复制到新的不可变类型实例。 如果有一个不可变对象需要对其进行超过2-3次“更改”(即更改引用,使其指向与原始不同的不可变),将数据复制到可变对象,更改它,并且将其存储回来通常比创建一个与原始文件在某种程度上不同的对象更加实用,然后创建一个与另一种方式不同的新对象,等等。

一个很好的易于推广的方法是定义一个(可能是内部的)暴露字段结构类型,然后让可变类和不可变类都包含该类型的字段。 必须为两种类类型单独定义访问器属性,但是可以将两种类型的GetHashCodeEquals方法链链接到底层结构的相应方法。 在给定另一个实例的情况下创建任一类型的实例可以使用单个结构分配来完成,而不必单独复制所有成员。

如果一个程序员理解结构如何工作(我认为这些知识是基本的,即使其他人不同意),也可以将结构类型公之于众。 即使结构是公共的,具有可变持有者类也可能是有用的,因为有时候引用语义是有用的(例如,人们可能希望能够更改存储在Dictionary而不必更改Dictionary本身),但是convert-to-mutable / change / convert-to-immutable模式对于结构类型比使用类类型更好。