What's the most concise way to create an immutable class in C#?

拟墨画扇 提交于 2019-12-06 23:39:55

问题


I find myself having to create a lot of immutable classes and I'd like to find a way to do it with no redundant information. I can't use an anonymous type because I need to return these classes from methods. I want intellisense support, so I'd prefer not to use Dictionaries, dynamic or anything like that. I also want well-named properties, which rules out Tuple<>. So far, some patterns I've tried:

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

These are both somewhat less verbose than the "standard" pattern of creating readonly fields, setting them via a constructor, and exposing them via properties. However, both of these methods still have lots of redundant boilerplate.

Any ideas?


回答1:


You could use automatic properties with private setters

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



回答2:


See if public {get;private set;} properties work for your case - a bit more compact than separate field declaration, exactly the same semantic.

Update: As ChaseMedallion commented and inlined in the question this approach does not provide automatically generated GetHashCode and Equals methods unlike Tuple approach.

class MyImmutable 
{
    public int MyProperty {get; private set;}

    public MyImmutable(int myProperty)
    {
       MyProperty = v;
    }
}

I like Tuple approach as it gives object that can safely used in interesting contexts and provides nice names. If I would need to create many types of such kind I would consider to re-implement Tuple classes:

  • at construction time pre-compute GetHashCode and store as part of object to avoid unbounded checks for collections/strings. Possibly optional to allow opt in for cases that are commonly used as keys in Dictionary.
  • hide generic names (i.e. protected or just hide from intellisence with EditorBrowsableAttribute) so there is no confusion about 2 set of names.
  • consider enforcing field types to be immutable in Debug builds/FxCop rules...

Side note: check out Eric Lippert's series on immutable types.




回答3:


Immutable classes are most useful when there exists a corresponding mutable class which whose contents can easily be loaded from an immutable-type instance or copied to a new immutable-type instance. If one has an immutable object needs to make more than 2-3 "changes" to it (i.e. change the reference so it points to an immutable which is suitably different from the original), copying the data to a mutable object, changing it, and storing it back is often more practical than creating an object which differs from the original in one way, then creating a new object which differs from that in another way, etc.

A nice easily-generalized approach is to define a (possibly internal) exposed-field structure type, and then have both the mutable and immutable classes hold a field of that type. The accessor properties would have to be defined separately for both class types, but one could have both types' GetHashCode and Equals methods chain to the corresponding methods of the underlying struct. Creating an instance of either type given an instance of the other could be accomplished using a single struct assignment, rather than having to copy all of the members individually.

If one's programmers understand how structures work (I'd consider such knowledge fundamental, even if others disagree) one could make the structure type public. Having a mutable holder class may be useful even if the structure is public, since there are times when reference semantics are useful (e.g. one may want to be able to change something stored in a Dictionary without having to change the Dictionary itself), but the convert-to-mutable/change/convert-to-immutable pattern works even better with structure types than with class types.



来源:https://stackoverflow.com/questions/15283109/whats-the-most-concise-way-to-create-an-immutable-class-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!