Comment créer une classe immuable?

113

Je travaille sur la création d'une classe immuable.
J'ai marqué toutes les propriétés en lecture seule.

J'ai une liste d'articles dans la classe.
Bien que si la propriété est en lecture seule, la liste peut être modifiée.

L'exposition du IEnumerable de la liste le rend immuable.
Je voulais savoir quelles sont les règles de base à suivre pour rendre une classe immuable?

Biswanath
la source
2
Je vous recommande fortement de lire la série de blogs d'Eric Lippert sur l'immuabilité , en particulier l'entrée sur les «genres d'immuabilité» . Votre commentaire selon lequel "Exposer le IEnumerable de la liste le rend immuable" me semble quelque peu étrange. Que voulez-vous dire par là?
Jon Skeet
Ce que je voulais dire était, plutôt que de permettre d'accéder à la liste (si l'objet a une liste de quelques autres objets), permettre à l'utilisateur d'accéder aux membres avec IEnumerable. Liste parlante ici comme dans un exemple spécifique, mais cela peut être n'importe quelle structure de données.
Biswanath
1
Les liens de Jon Skeet vers la série de blogs d'Eric Lippert se sont rompus lorsque MSDN a archivé ces blogs. Veuillez voir "types d'immuabilité" . Le reste de la série semble être sous décembre 2007 + janvier 2008 dans le panneau de gauche.
John Doe le
S'il vous plaît voir la série de blog de Eric Lippert sur la façon dont les gens confondent souvent les termes atomicity, volatility, et immutability: Première partie , deuxième partie et troisième partie . Ceux-ci proviennent de son blog personnel et, je crois, plus conviviaux pour les débutants que ses messages MSDN.
John Doe le

Réponses:

121

Je pense que vous êtes sur la bonne voie -

  • toutes les informations injectées dans la classe doivent être fournies dans le constructeur
  • toutes les propriétés doivent être des getters uniquement
  • si une collection (ou un tableau) est passée au constructeur, elle doit être copiée pour empêcher l'appelant de la modifier ultérieurement
  • si vous allez renvoyer votre collection, renvoyez une copie ou une version en lecture seule (par exemple, en utilisant ArrayList.ReadOnly ou similaire - vous pouvez combiner cela avec le point précédent et stocker une copie en lecture seule à renvoyer lorsque les appelants y accèdent), renvoyer un énumérateur ou utiliser une autre méthode / propriété qui autorise l'accès en lecture seule à la collection
  • gardez à l'esprit que vous pouvez toujours avoir l'apparence d'une classe mutable si l'un de vos membres est mutable - si tel est le cas, vous devez copier l'état que vous souhaitez conserver et éviter de renvoyer des objets mutables entiers, sauf si vous les copiez avant de les rendre à l'appelant - une autre option est de ne renvoyer que des "sections" immuables de l'objet mutable - merci à @Brian Rasmussen de m'avoir encouragé à développer ce point
Blair Conrad
la source
Dois-je écrire une propriété de recherche de wrapper, qui vous permet uniquement de rechercher? Et merci pour la réponse.
Biswanath
5
Tout type de référence mutable passé comme argument au constructeur doit être copié. Sinon, l'appelant conservera toujours une référence à l'état.
Brian Rasmussen
@Brian Rasmussen, vous avez raison, mais même s'il est copié, il peut être possible pour n'importe quel appelant d'accéder à l'objet mutable, en fonction des accès fournis. Dans le cas où un objet mutable est passé, la classe est préférable de toujours renvoyer une copie différente, ou des sections immuables de l'objet
Blair Conrad
@Blair - Si j'ai un dictionnaire dans la classe que je veux juste utiliser pour la recherche. Une propriété en lecture seule qui effectue une recherche devrait-elle convenir?
Biswanath
@Biswanath - oui, avec la mise en garde que si les valeurs du dictionnaire sont modifiables, vous devrez les protéger avant de revenir, si vous ne voulez pas qu'elles mutent
Blair Conrad
18

Pour être immuable, toutes vos propriétés et champs doivent être en lecture seule. Et les éléments de toute liste doivent eux-mêmes être immuables.

Vous pouvez créer une propriété de liste en lecture seule comme suit:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}
Joe
la source
1
Je pense qu'ImmutableList serait mieux.
Kevin Wong
utilisez ImmutableList, envisagez également de sceller la classe
kofifus
Je ne suis pas convaincu que ImmutableList est un bon moyen pour ce cas d' utilisation: basic rules to make a class (that contains a list) immutable. La sémantique ImmutableList permet une utilisation plus efficace de la mémoire lors de l'ajout d'éléments à une copie de la liste, mais cela me semble être un cas d'utilisation plus spécifique.
Joe
11

Gardez également à l'esprit que:

public readonly object[] MyObjects;

n'est pas immuable même s'il est marqué avec un mot-clé en lecture seule. Vous pouvez toujours modifier les références / valeurs de tableau individuelles par l'accesseur d'index.

lubos hasko
la source
5

Utilisez la ReadOnlyCollectionclasse. Il est situé dans l' System.Collections.ObjectModelespace de noms.

Sur tout ce qui renvoie votre liste (ou dans le constructeur), définissez la liste comme une collection en lecture seule.

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}
mbillard
la source
1

Une autre option serait d'utiliser un modèle de visiteur au lieu d'exposer des collections internes du tout.

Andrew
la source
1

L'utilisation de ReadOnlyCollection empêchera le client de le modifier.

Imad
la source