Comment gérer les setters sur des champs immuables?

25

J'ai une classe avec deux readonly intchamps. Ils sont exposés en tant que propriétés:

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

Cependant, cela signifie que le code suivant est parfaitement légal:

Thing iThoughtThisWasMutable = new Thing(1, 42);

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

Les bogues qui découlent de l' hypothèse que cela fonctionnerait seront certainement insidieux. Bien sûr, le développeur erroné aurait dû lire les documents. Mais cela ne change pas le fait qu'aucune erreur de compilation ou d'exécution ne l'a averti du problème.

Comment Thingchanger la classe pour que les développeurs soient moins susceptibles de faire l'erreur ci-dessus?

Jetez une exception? Utilisez une méthode getter au lieu d'une propriété?

kdbanman
la source
1
Merci, @gnat. Ce poste ( à la fois la question et la réponse) semble parler sur les interfaces, comme dans Capital I Interfaces. Je ne suis pas sûr que c'est ce que je fais.
kdbanman
6
@gnat, c'est un conseil terrible. Les interfaces offrent un excellent moyen de servir des VO / DTO immuables publiquement qui sont toujours faciles à tester.
David Arno
4
@gnat, je l'ai fait et la question n'a aucun sens car l'OP semble penser que les interfaces vont détruire l'immuabilité, ce qui est absurde.
David Arno
1
@gnat, cette question portait sur la déclaration des interfaces pour les DTO. La réponse la plus votée était que les interfaces ne seraient pas nuisibles, mais probablement inutiles.
Paul Draper

Réponses:

107

Pourquoi rendre ce code légal?
Sortez le set { }si ça ne fait rien.
Voici comment définir une propriété publique en lecture seule:

public int Foo { get { return _foo; } }
paparazzo
la source
3
Je ne pensais pas que c'était légal pour une raison quelconque, mais cela semble fonctionner parfaitement! Parfait, merci.
kdbanman
1
@kdbanman Cela a probablement quelque chose à voir avec quelque chose que vous avez vu l'IDE faire à un moment donné - probablement l'auto-complétion.
Panzercrisis
2
@kdbanman; ce qui n'est pas légal (jusqu'à C # 5) est public int Foo { get; }(auto-propriété avec seulement un getter) mais l' public int Foo { get; private set; }est.
Laurent LA RIZZA
@LaurentLARIZZA, vous avez rafraîchi ma mémoire. C'est exactement de là que vient ma confusion.
kdbanman
45

Avec C # 5 et avant, nous étions confrontés à deux options pour les champs immuables exposés via un getter:

  1. Créez une variable de support en lecture seule et renvoyez-la via un getter manuel. Cette option est sécurisée (il faut supprimer explicitement le readonlypour détruire l'immuabilité. Cela a cependant créé beaucoup de code de plaque de chaudière.
  2. Utilisez une propriété automatique, avec un setter privé. Cela crée du code plus simple, mais il est plus facile de casser accidentellement l'immuabilité.

Cependant, avec C # 6 (disponible dans VS2015, qui a été publié hier), nous obtenons maintenant le meilleur des deux mondes: les propriétés automatiques en lecture seule. Cela nous permet de simplifier la classe OP pour:

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

Les lignes Foo = fooet Bar = barne sont valides que dans le constructeur (qui satisfait à l'exigence robuste en lecture seule) et le champ de support est implicite, plutôt que d'avoir à être explicitement défini (ce qui permet d'obtenir le code le plus simple).

David Arno
la source
2
Et les constructeurs principaux peuvent simplifier encore plus cela, en se débarrassant de la surcharge syntaxique du constructeur.
Sebastian Redl
1
@DanLyons Damn, j'étais impatient de l'utiliser dans notre base de code.
Sebastian Redl
12

Vous pourriez simplement vous débarrasser des colons. Ils ne font rien, ils dérouteront les utilisateurs et entraîneront des bugs. Cependant, vous pouvez plutôt les rendre privés et ainsi vous débarrasser des variables de support, simplifiant votre code:

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; }
}
David Arno
la source
1
" public int Foo { get; private set; }" Cette classe n'est plus immuable car elle peut se changer. Merci quand même!
kdbanman
4
La classe décrite est immuable, car elle ne se change pas.
David Arno
1
Ma classe actuelle est un peu plus complexe que le MWE que j'ai donné. Je recherche une réelle immuabilité , avec une sécurité des threads et des garanties internes à la classe.
kdbanman
4
@kdbanman, et votre point est? Si votre classe n'écrit qu'aux installateurs privés pendant la construction, alors elle a mis en œuvre une "immuabilité réelle". Le readonlymot-clé est juste un joug poka, c'est-à-dire qu'il empêche la classe de briser l'immutabilité à l'avenir; il n'est cependant pas nécessaire pour atteindre l'immuabilité.
David Arno
3
Terme intéressant, j'ai dû le rechercher sur Google - poka-yoke est un terme japonais qui signifie "à l'abri des erreurs". Tu as raison! C'est exactement ce que je fais.
kdbanman
6

Deux solutions.

Simple:

N'incluez pas les setters comme l'a noté David pour les objets immuables en lecture seule.

Alternativement:

Permet aux setters de renvoyer un nouvel objet immuable, verbeux par rapport à l'ancien mais fournit un état dans le temps pour chaque objet initialisé. Cette conception est un outil très utile pour la sécurité et l'immuabilité des threads qui s'étend sur tous les langages OOP impératifs.

Pseudocode

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

usine statique publique

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...
}
Matt Smithies
la source
2
setXYZ sont des noms horribles pour les méthodes d'usine, pensez à withXYZ ou similaire.
Ben Voigt du
Merci, mis à jour ma réponse pour refléter votre commentaire utile. :-)
Matt Smithies
La question portait spécifiquement sur les poseurs de biens , et non sur les méthodes de passation.
user253751
Certes, mais l'OP s'inquiète de l'état mutable d'un éventuel futur développeur. Les variables utilisant des mots-clés privés en lecture seule ou privés doivent être auto-documentées, ce n'est pas la faute des développeurs d'origine si cela n'est pas compris. Comprendre les différentes méthodes pour changer d'état est essentiel. J'ai ajouté une autre approche qui est très utile. Il y a beaucoup de choses dans le monde du code qui provoquent une paranoïa excessive, les vars privés en lecture seule ne devraient pas en faire partie.
Matt Smithies