Come gestisco i setter su campi immutabili?


25

Ho una lezione con due readonly intcampi. 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 quanto segue è un codice perfettamente legale:

Thing iThoughtThisWasMutable = new Thing(1, 42);

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

Gli insetti che derivano dal presupporre che funzionerebbe sono sicuramente insidiosi. Certo, lo sviluppatore errato avrebbe dovuto leggere i documenti. Ma ciò non cambia il fatto che nessun errore di compilazione o di runtime lo ha avvertito del problema.

Come dovrebbe Thingessere cambiata la classe in modo che gli sviluppatori abbiano meno probabilità di commettere l'errore di cui sopra?

Generare un'eccezione? Utilizzare un metodo getter anziché una proprietà?



1
Grazie @gnat. Quel post (sia la domanda e la risposta) sembra essere parlare di interfacce, come in Capital I Interfaces. Non sono sicuro che sia quello che sto facendo.
kdbanman,

6
@gnat, questo è un consiglio terribile. Le interfacce offrono un ottimo modo per servire VO / DTO immutabili pubblicamente che sono ancora facili da testare.
David Arno,

4
@gnat, l'ho fatto e la domanda non ha senso poiché l'OP sembra pensare che le interfacce distruggeranno l'immutabilità, il che è una sciocchezza.
David Arno,

1
@gnat, quella domanda riguardava la dichiarazione di interfacce per DTO. La risposta più votata è stata che le interfacce non sarebbero dannose, ma probabilmente non necessarie.
Paul Draper,

Risposte:


107

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

public int Foo { get { return _foo; } }

3
Non pensavo che fosse legale per qualche motivo, ma sembra funzionare perfettamente! Perfetto, grazie.
kdbanman,

1
@kdbanman Probabilmente ha qualcosa a che fare con qualcosa che hai visto fare l'IDE ad un certo punto - probabilmente il completamento automatico.
Panzercrisis,

2
@kdbanman; ciò che non è legale (fino a C # 5) è public int Foo { get; }(auto-proprietà con solo un getter) Ma lo public int Foo { get; private set; }è.
Laurent LA RIZZA,

@LaurentLARIZZA, hai fatto jogging nella mia memoria. Ecco esattamente da dove viene la mia confusione.
kdbanman,

45

Con C # 5 e precedenti, 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 il readonlyper distruggere l'immutabilità. Tuttavia, ha creato un sacco di codice della piastra della caldaia.
  2. Utilizzare una proprietà automatica, con un setter privato. Questo crea un codice più semplice, ma è più facile interrompere accidentalmente l'immutabilità.

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

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 linee Foo = fooe Bar = barsono valide solo nel costruttore (che raggiunge il solido requisito di sola lettura) e il campo di supporto è implicito, piuttosto che dover essere esplicitamente definito (che ottiene il codice più semplice).


2
E i costruttori primari possono semplificarlo ulteriormente, eliminando il sovraccarico sintattico del costruttore.
Sebastian Redl,


1
@DanLyons Accidenti, non vedevo l'ora di usarlo in tutto il nostro codice.
Sebastian Redl,

12

Potresti semplicemente sbarazzarti dei setter. Non fanno nulla, confonderanno gli utenti e porteranno a bug. Tuttavia, potresti invece renderli privati ​​e quindi sbarazzarti delle 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; }
}

1
" public int Foo { get; private set; }" Quella classe non è più immutabile perché può cambiare se stessa. Grazie comunque!
kdbanman,

4
La classe descritta è immutabile, in quanto non cambia se stessa.
David Arno,

1
La mia classe attuale è un po 'più complessa del MWE che ho dato. Sto cercando la vera immutabilità , completa di sicurezza del thread e garanzie interne alla classe.
kdbanman,

4
@kdbanman, e il tuo punto è? Se la tua classe scrive solo ai setter privati ​​durante la costruzione, allora ha implementato la "vera immutabilità". La readonlyparola chiave è solo un poka-giogo, ovvero protegge dalla classe che rompe l'immutabilità in futuro; non è necessario per raggiungere l'immutabilità però.
David Arno,

3
Termine interessante, ho dovuto cercarlo su Google - poka-yoke è un termine giapponese che significa "a prova di errore". Hai ragione! Questo è esattamente quello che sto facendo.
kdbanman,

6

Due soluzioni.

Semplice:

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

In alternativa:

Consenti 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...
}

fabbrica statica pubblica

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

2
setXYZ sono nomi orribili per i metodi di fabbrica, considera con XYZ o simili.
Ben Voigt,

Grazie, ho aggiornato la mia risposta per riflettere il tuo utile commento. :-)
Matt Smithies,

La domanda si poneva specificamente sui setter di proprietà , non sui metodi di setter.
user253751

È vero, ma l'OP è preoccupato per lo stato mutevole da un possibile sviluppatore futuro. Le variabili che usano la sola lettura privata o le parole chiave finali private devono essere auto-documentate, non è colpa degli sviluppatori originali se ciò non è compreso. È fondamentale comprendere diversi metodi per cambiare stato. Ho aggiunto un altro approccio che è molto utile. Ci sono molte cose nel mondo del codice che causano un'eccessiva paranoia, i privati ​​di sola lettura non dovrebbero essere una di queste.
Matt Smithies,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.