Come gestisco i setter su campi immutabili?

24

Ho una classe con due campi readonly int . Sono esposti come proprietà:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

Tuttavia, ciò significa che il seguente codice è perfettamente legale:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

I bug che provengono da assumendo che funzionerebbero sarebbero sicuramente insidiosi. Certo, lo sviluppatore errato dovrebbe aver letto i documenti. Ma ciò non cambia il fatto che nessun errore di compilazione o di runtime lo ha avvertito del problema.

Come deve essere modificata la classe Thing in modo che gli sviluppatori abbiano meno probabilità di commettere l'errore sopra riportato?

Getta un'eccezione? Usa un metodo getter invece di una proprietà?

    
posta kdbanman 21.07.2015 - 19:34
fonte

4 risposte

107

Perché rendere legale quel codice?
Elimina set { } se non fa nulla.
Ecco come si definisce una proprietà pubblica di sola lettura:

public int Foo { get { return _foo; } }
    
risposta data 21.07.2015 - 19:39
fonte
45

Con C # 5 e prima, ci siamo trovati di fronte a due opzioni per i campi immutabili esposti tramite un getter:

  1. Creare una variabile di supporto di sola lettura e restituirla tramite un getter manuale. Questa opzione è sicura (si deve rimuovere esplicitamente readonly per distruggere l'immutabilità, ma ha creato un sacco di codice per la piastra della caldaia.
  2. Utilizza una proprietà automatica, con un setter privato. Questo crea codice più semplice, ma è più facile rompere accidentalmente l'immutabilità.

Con C # 6 (disponibile in VS2015, che è stato rilasciato ieri), ora otteniamo il meglio da entrambi i mondi: proprietà auto di sola lettura. Questo ci consente di semplificare la classe dell'OP a:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Le righe Foo = foo e Bar = bar sono valide solo nel costruttore (che raggiunge il requisito di sola lettura robusto) e il campo di supporto è implicito, piuttosto che dover essere definito in modo esplicito (che consente di ottenere il codice più semplice).

    
risposta data 21.07.2015 - 20:39
fonte
12

Potresti semplicemente sbarazzarti dei setter. Non fanno nulla, confonderanno gli utenti e porteranno a bug. Tuttavia, potresti invece renderli privati e quindi eliminare le variabili di supporto, semplificando il tuo codice:

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}
    
risposta data 21.07.2015 - 19:43
fonte
6

Due soluzioni.

Semplice:

Non includere i setter come notato da David per gli oggetti immutabili di sola lettura.

In alternativa:

Permetti ai setter di restituire un nuovo oggetto immutabile, dettagliato rispetto al primo ma fornisce uno stato nel tempo per ogni oggetto inizializzato. Questo design è uno strumento molto utile per la sicurezza e l'immutabilità dei thread che si estende a tutti i linguaggi OOP imperativi.

Pseudocodice

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

factory statico pubblico

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}
    
risposta data 22.07.2015 - 12:57
fonte

Leggi altre domande sui tag