2013-03-07 11 views
8

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?

+2

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.) –

+0

@JonSkeet SetOnce impedisce il recupero prima dell'impostazione (ammetto il post). – ChaseMedallion

+1

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. –

risposta

1

Vedere se le proprietà public {get;private set;} funzionano per il vostro caso - un po 'più compatto di una dichiarazione di campo separata, esattamente la stessa semantica.

Aggiornamento: Come ChaseMedallion ha commentato e sottolineato nella domanda questo approccio non fornisce automaticamente i metodi GetHashCode e Equals a differenza dell'approccio Tuple.

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

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

mi piace Tuple approccio in quanto dà oggetto che può tranquillamente utilizzato in contesti interessanti e fornisce i nomi di belle. Se avrei bisogno di creare molti tipi di questo tipo riterrei re-implementare Tuple classi:

  • in fase di costruzione pre-elaborazione GetHashCode e conservare come parte di oggetto per evitare controlli illimitate per le collezioni/stringhe. Forse facoltativo per consentire l'attivazione di casi comunemente usati come chiavi in ​​Dictionary.
  • nascondere nomi generici (ad esempio protected o semplicemente nascondersi dall'intelligenza con EditorBrowsableAttribute), quindi non c'è confusione circa 2 set di nomi.
  • considerano far rispettare i tipi di campo di essere immutabile nel build di debug regole/FxCop ...

Nota a margine: il check-out di Eric Lippert series on immutable types.

4

È possibile utilizzare le proprietà automatiche con incastonatori privati ​​

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

Io uso solo questo tipo di proprietà. Scrivere meno codice e gestire i membri nella stessa modalità è meglio .. –

+0

Sono a conoscenza di questo modello, ma sembra piuttosto dettagliato come il modello Tuple <>, ma occupa molte più righe e non ti ottiene Uguali() e GetHashCode() – ChaseMedallion

1

classi immutabili sono più utili quando esiste una corrispondente classe mutevole che il cui contenuto può essere facilmente caricata da un'istanza immutabile tipo o copiato in un immutabile -tipo di istanza. Se uno ha un oggetto immutabile ha bisogno di fare più di 2-3 "cambiamenti" ad esso (cioè cambiare il riferimento in modo che punti ad un immutabile che sia adeguatamente diverso dall'originale), copiando i dati su un oggetto mutevole, cambiandolo, e riporlo indietro è spesso più pratico della creazione di un oggetto che differisce dall'originale in un modo, quindi crea un nuovo oggetto che differisce da quello in un altro modo, ecc.

Un buon approccio facilmente generalizzato è quello di definire un (possibilmente interno) tipo di struttura di campo esposto, e quindi avere entrambe le classi mutabili e immutabili tenere un campo di quel tipo. Le proprietà di accesso dovrebbero essere definite separatamente per entrambi i tipi di classe, ma è possibile avere entrambi i tipi 'GetHashCode e la catena di metodi Equals ai metodi corrispondenti della struttura sottostante.La creazione di un'istanza di entrambi i tipi data un'istanza dell'altro potrebbe essere eseguita utilizzando una singola assegnazione di struttura, anziché dover copiare tutti i membri singolarmente.

Se i programmatori capiscono come funzionano le strutture (considererei fondamentale tale conoscenza, anche se gli altri non sono d'accordo), si potrebbe rendere pubblica la struttura. Avere una classe portabile mutevole può essere utile anche se la struttura è pubblica, poiché ci sono momenti in cui la semantica di riferimento è utile (ad esempio si potrebbe voler essere in grado di modificare qualcosa memorizzato in unosenza dover cambiare lo stesso), ma il convert-to-mutable/change/convert-to-immutable pattern funziona ancora meglio con i tipi di struttura che con i tipi di classe.

Problemi correlati