Mi trovo a dover creare un sacco di classi immutabili e mi piacerebbe trovare un modo per farlo senza informazioni ridondanti. Non posso usare un tipo anonimo perché ho bisogno di restituire queste classi dai metodi. Voglio supporto intellisense, quindi preferirei non usare dizionari, dinamici o simili. Voglio anche proprietà ben definite, che escludono la tupla <>. Finora, alcuni modelli ho provato:Qual è il modo più conciso per creare una classe immutabile in C#?
// 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; }
}
Questi sono entrambi un po 'meno prolissa rispetto al modello "standard" di produrre campi di sola lettura, impostandole tramite un costruttore, ed esponendoli tramite le proprietà. Tuttavia, entrambi questi metodi hanno ancora un numero di piastre ridondanti.
Qualche idea?
Il tuo secondo modulo non è immutabile: puoi osservare un cambiamento recuperando 'Field1', quindi impostarlo, quindi recuperarlo di nuovo. (A meno che 'SetOnce' impedisca il recupero prima di un set, che non hai descritto.) –
@JonSkeet SetOnce impedisce il recupero prima dell'impostazione (ammetto il post). – ChaseMedallion
Sfortunatamente non esiste una soluzione slam-dunk per il problema che si pone. Il team di progettazione linguistica è ben consapevole di ciò. Spero che questo un giorno venga affrontato. –