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

本文关键字:简洁 是什么 方法 创建 不可变 | 更新日期: 2023-09-27 18:02:56

我发现自己不得不创建很多不可变的类,我想找到一种没有冗余信息的方法。我不能使用匿名类型,因为我需要从方法返回这些类。我想要智能感知支持,所以我不喜欢使用字典、动态或类似的东西。我还需要命名良好的属性,这就排除了Tuple<>。到目前为止,我尝试了一些模式:

// inherit Tuple<>. This has the added benefit of giving you Equals() and GetHashCode()
public class MyImmutable : Tuple<int, string, bool> {
   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<T> 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<int> _field1;
    private SetOnce<string> _field2;
    private SetOnce<bool> _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<T> type which allows you to
// easily expose types with simple get/set properties as immutable
public class Immutable<T> {
    private readonly Dictionary<PropertyInfo, object> _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<TProperty>(Expression<Func<T, TProperty>> 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<Mutable>(new Mutable { A = 5 });
var a = immutable.Get(m => m.A);
// 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; }
}

比起创建只读字段、通过构造函数设置字段并通过属性公开字段的"标准"模式,这两种方式都要简单一些。然而,这两种方法仍然有许多冗余的样板文件。

任何想法?

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

你可以在私有设置中使用自动属性

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在问题中评论和内联的那样,这种方法不像Tuple方法那样提供自动生成的GetHashCodeEquals方法。

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

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

  • 在构建时预先计算GetHashCode并作为对象的一部分存储,以避免对集合/字符串进行无界检查。可能是可选的,允许在Dictionary中通常用作键的情况下选择。
  • 隐藏通用名称(即protected或只是隐藏从智能与EditorBrowsableAttribute),所以没有混淆的2组名称。
  • 考虑强制字段类型在调试构建/FxCop规则中不可变…

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

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

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

如果一个程序员理解结构是如何工作的(我认为这些知识是基本的,即使其他人不同意),他可以将结构类型设为public。即使结构是公共的,拥有一个可变的持有者类也可能是有用的,因为有时引用语义是有用的(例如,人们可能希望能够改变Dictionary中存储的东西,而不必改变Dictionary本身),但是转换为可变/改变/转换为不可变模式在结构类型中比在类类型中工作得更好。