L’immuabilité est-elle très utile en l’absence de concurrence?

53

Il semble que la sécurité des threads soit toujours / souvent mentionnée comme le principal avantage de l'utilisation de types immuables et notamment de collections.

J'ai une situation où je voudrais m'assurer qu'une méthode ne modifiera pas un dictionnaire de chaînes (qui sont immuables en C #). J'aimerais limiter les choses autant que possible.

Cependant, je ne suis pas sûr que l'ajout d'une dépendance à un nouveau package (Microsoft Immutable Collections) en vaille la peine. La performance n'est pas un gros problème non plus.

Donc, je suppose que ma question est de savoir si les collections immuables sont fortement recommandées lorsqu'il n'y a pas d'exigences de performances strictes et qu'il n'y a pas de problèmes de sécurité des threads? Considérez que la sémantique de valeur (comme dans mon exemple) pourrait ou non être une exigence.

Tanière
la source
1
Modification simultanée ne signifie pas forcément thread. Il suffit de regarder le nom bien nommé ConcurrentModificationExceptionqui est généralement causé par le même thread qui mute la collection dans le même thread, dans le corps d'une foreachboucle sur la même collection.
1
Je veux dire que vous ne vous trompez pas, mais c'est différent de ce que demande OP. Cette exception est levée car la modification lors de l'énumération n'est pas autorisée. L'utilisation d'un ConcurrentDictionary, par exemple, aurait toujours cette erreur.
edthethird
13
Peut-être devriez-vous également vous poser la question opposée: quand la mutabilité vaut-elle la peine?
Giorgio
2
En Java, si la mutabilité affecte hashCode()ou si le equals(Object)résultat est modifié, des erreurs peuvent survenir lors de l'utilisation Collections(par exemple, HashSetl'objet a été stocké dans un "compartiment" et, après la mutation, il doit être remplacé par un autre).
SJuan76
2
@ DavorŽdralo En ce qui concerne les abstractions de langages de haut niveau, l'immutabilité omniprésente est plutôt docile. Il s’agit simplement d’une extension naturelle de l’abstraction très commune (présente même en C) de la création et de la suppression silencieuse de "valeurs temporaires". Vous voulez peut-être dire que c’est une façon inefficace d’utiliser un processeur, mais cet argument a aussi des défauts: les langages gais, mais capables de mutabilité, fonctionnent souvent moins bien que les langages immuables, mais statiques, en partie parce qu’il existe des astucieux (mais finalement très simples) astuces pour optimiser les programmes en manipulant des données immuables: types linéaires, déforestation, etc.

Réponses:

101

L'immuabilité simplifie la quantité d'informations que vous devez suivre mentalement lors de la lecture ultérieure du code . Pour les variables modifiables, et en particulier les membres de la classe modifiables, il est très difficile de savoir dans quel état elles se trouveront sur la ligne spécifique que vous lisez, sans exécuter le code avec un débogueur. Il est facile de raisonner sur des données immuables - ce sera toujours la même chose. Si vous voulez le changer, vous devez créer une nouvelle valeur.

Honnêtement, je préférerais rendre les choses immuables par défaut , puis les changer en mutables là où il est prouvé qu'elles doivent l'être, que cela signifie que vous avez besoin de la performance ou qu'un algorithme que vous avez n'a pas de sens pour l'immutabilité.

KChaloux
la source
23
+1 La simultanéité est une mutation simultanée, mais une mutation qui se propage dans le temps peut être tout aussi difficile à raisonner
guillaume31
20
Pour développer ceci: une fonction qui repose sur une variable mutable peut être considérée comme prenant un argument caché supplémentaire, qui est la valeur actuelle de la variable mutable. Toute fonction qui modifie une variable mutable peut également être considérée comme produisant une valeur de retour supplémentaire, c'est-à-dire la nouvelle valeur de l'état mutable. Lorsque vous examinez un morceau de code, vous ne savez pas si cela dépend ou si vous modifiez l'état de mutable. Vous devez donc vous renseigner, puis suivre les modifications mentalement. Cela introduit également le couplage entre deux morceaux de code partageant un état mutable, et le couplage est mauvais.
Doval
29
@Mehrdad People a également réussi à créer de grands programmes d'assemblage pendant des décennies. Puis nous avons fait deux décennies de C.
Doval
11
@ Mehrdad La copie d'objets entiers n'est pas une bonne option lorsque les objets sont volumineux. Je ne vois pas pourquoi l'ordre de grandeur impliqué dans l'amélioration est important. Refuseriez-vous une amélioration de 20% (remarque: nombre arbitraire) de productivité simplement parce que ce n'était pas une amélioration à trois chiffres? L’immuabilité est un défaut normal ; vous pouvez vous en écarter, mais vous avez besoin d'une raison.
Doval
9
@Giorgio Scala m'a fait comprendre à quel point il est rarement nécessaire de créer une valeur mutable. Chaque fois que j'utilise ce langage, je fais tout un val, et seulement à de très rares occasions, je trouve que je dois changer quelque chose en un var. Un grand nombre des «variables» que je définis dans une langue donnée ne sont là que pour contenir une valeur qui stocke le résultat d'un calcul, et n'a pas besoin d'être mise à jour.
KChaloux
22

Votre code doit exprimer votre intention. Si vous ne voulez pas qu'un objet soit modifié une fois créé, rendez-le impossible.

L’immuabilité présente plusieurs avantages:

  • L'intention de l'auteur original est mieux exprimée.

    Comment pourriez-vous savoir que dans le code suivant, la modification du nom entraînerait l'application à générer une exception quelque part plus tard?

    public class Product
    {
        public string Name { get; set; }
    
        ...
    }
    
  • Il est plus facile de s’assurer que l’objet n’apparaîtrait pas dans un état non valide.

    Vous devez contrôler cela dans un constructeur, et seulement là. Par contre, si vous avez un ensemble de paramètres et de méthodes qui modifient l’objet, de tels contrôles peuvent devenir particulièrement difficiles, notamment lorsque, par exemple, deux champs doivent changer en même temps pour que l’objet soit valide.

    Par exemple, un objet est valide si l'adresse n'est pas null ou si les coordonnées GPS ne le sont pas null, mais il est invalide si l'adresse et les coordonnées GPS sont spécifiées. Pouvez-vous imaginer l'enfer pour valider cela si l'adresse et les coordonnées GPS ont un setter, ou les deux sont mutables?

  • La concurrence.

À propos, dans votre cas, vous n’avez pas besoin de paquets tiers. .NET Framework comprend déjà une ReadOnlyDictionary<TKey, TValue>classe.

Arseni Mourzenko
la source
1
+1, surtout pour "Vous devez contrôler cela dans un constructeur, et seulement là." OMI c'est un avantage énorme.
Giorgio
10
Autre avantage: copier un objet est gratuit. Juste un pointeur.
Robert Grant
1
@MainMa Merci pour votre réponse, mais autant que je sache, ReadOnlyDictionary ne donne aucune garantie que quelqu'un d'autre ne changera pas le dictionnaire sous-jacent (même sans concurrence, je souhaiterais peut-être enregistrer la référence au dictionnaire d'origine dans l'objet auquel appartient la méthode pour une utilisation ultérieure). ReadOnlyDictionary est même déclaré dans un espace de noms étrange: System.Collections.ObjectModel.
Den
2
@ Den: Cela concerne l'une de mes bévues: les gens qui considèrent "lecture seule" et "immuable" comme synonymes. Si un objet est encapsulé dans un wrapper en lecture seule et qu'aucune autre référence n'existe ou n'est conservée nulle part dans l'univers, le wrapping le rendra immuable, et une référence au wrapper peut être utilisée comme un raccourci pour encapsuler l'état de l'objet. objet qui y est contenu. Cependant, il n’existe aucun mécanisme permettant au code de déterminer si tel est le cas. Au contraire, parce que le wrapper cache le type de l'objet enveloppé, enveloppant un objet immuable ...
Supercat
2
... empêchera le code de savoir si le wrapper résultant peut être considéré comme immuable en toute sécurité.
Supercat
13

Il y a de nombreuses raisons d'utiliser l'immutabilité dans un seul thread. Par exemple

L'objet A contient l'objet B.

Le code externe interroge votre objet B et vous le renvoyez.

Maintenant, vous avez trois situations possibles:

  1. B est immuable, pas de problème.
  2. B est modifiable, vous en faites une copie défensive et vous le rendez. La performance a frappé mais aucun risque.
  3. B est mutable, vous le retournez.

Dans le troisième cas, le code utilisateur peut ne pas réaliser ce que vous avez fait et peut modifier l'objet, ce qui modifie les données internes de votre objet, sans aucun contrôle ni visibilité sur ce qui se passe.

Tim B
la source
9

L’immuabilité peut également grandement simplifier la mise en œuvre des éboueurs. Du wiki de GHC :

[...] L'immuabilité des données nous oblige à produire beaucoup de données temporaires, mais elle permet également de collecter rapidement ces déchets. L'astuce est que les données immuables ne pointent JAMAIS vers des valeurs plus jeunes. En effet, les valeurs les plus jeunes n'existent pas encore au moment où une ancienne valeur est créée, elle ne peut donc pas être pointée de zéro. Et comme les valeurs ne sont jamais modifiées, il ne peut pas être signalé plus tard. C'est la propriété clé des données immuables.

Cela simplifie grandement le ramassage des ordures (GC). À tout moment, nous pouvons analyser les dernières valeurs créées et libérer celles qui ne sont pas pointées du même ensemble (bien entendu, les racines réelles de la hiérarchie des valeurs réelles sont stockées dans la pile). [...] Son comportement est donc contre-intuitif: plus le pourcentage de vos valeurs est élevé, plus le résultat est rapide. [...]

Petr Pudlák
la source
5

En développant ce que KChaloux a très bien résumé ...

Idéalement, vous avez deux types de champs, et donc deux types de code les utilisant. Les deux champs sont immuables et le code n'a pas à prendre en compte la mutabilité; ou les champs sont modifiables, et nous devons écrire du code qui prend un instantané ( int x = p.x) ou gère gracieusement de telles modifications.

D'après mon expérience, la plupart du code se situe entre les deux, étant optimiste : il référence librement des données mutables, en supposant que le premier appel à p.xaura le même résultat que le deuxième appel. Et la plupart du temps, c'est vrai, sauf quand il s'avère que ça ne l'est plus. Oops.

Alors, vraiment, retournez cette question: quelles sont mes raisons pour rendre ce mutable ?

  • Réduire la mémoire alloc / free?
  • Mutable par nature? (p.ex. compteur)
  • Enregistre les modificateurs, le bruit horizontal? (const / final)
  • Rend un code plus court / plus facile? (init par défaut, éventuellement écraser après)

Est-ce que vous écrivez du code défensif? L’immuabilité vous épargnera quelques copies. Est-ce que vous écrivez du code optimiste? L'immuabilité vous épargnera la folie de ce bug étrange et impossible.

JvR
la source
3

Un autre avantage de l'immuabilité est qu'il s'agit de la première étape pour arrondir ces objets immuables dans un pool. Ensuite, vous pouvez les gérer de manière à ne pas créer plusieurs objets qui représentent de manière conceptuelle et sémantique la même chose. Un bon exemple serait la chaîne Java.

Il est bien connu en linguistique que quelques mots apparaissent souvent, peuvent également apparaître dans un autre contexte. Ainsi, au lieu de créer plusieurs Stringobjets, vous pouvez en utiliser un immuable. Mais vous devez alors garder un gestionnaire de pool pour prendre soin de ces objets immuables.

Cela vous fera économiser beaucoup de mémoire. C'est aussi un article intéressant à lire: http://en.wikipedia.org/wiki/Zipf%27s_law

InforméA
la source
1

Dans Java, C # et d'autres langages similaires, les champs de type classe peuvent être utilisés pour identifier des objets, ou pour encapsuler des valeurs ou des états dans ces objets, mais les langages ne font aucune distinction entre ces utilisations. Supposons qu'un objet de classe Georgepossède un champ de type char[] chars;. Ce champ peut encapsuler une séquence de caractères de l'une des manières suivantes:

  1. Un tableau qui ne sera jamais modifié, ni exposé à un code susceptible de le modifier, mais pour lequel des références externes peuvent exister.

  2. Un tableau pour lequel il n'existe aucune référence externe, mais que George peut modifier librement.

  3. Un tableau appartenant à George, mais auquel il peut exister des vues extérieures qui représenteraient l'état actuel de George.

De plus, la variable peut, au lieu d'encapsuler une séquence de caractères, encapsuler une vue en direct dans une séquence de caractères appartenant à un autre objet.

Si charsactuellement encapsule la séquence de caractères [vent], et que George souhaite charsencapsuler la séquence de caractères [baguette magique], il y a un certain nombre de choses que George pourrait faire:

A. Construisez un nouveau tableau contenant les caractères [baguette] et changez charspour identifier ce tableau plutôt que l'ancien.

B. Identifiez en quelque sorte un tableau de caractères préexistant qui contiendra toujours les caractères [baguette magique] et changez charspour identifier ce tableau plutôt que l'ancien.

C. Modifier le deuxième caractère du tableau identifié par charsun a.

Dans le cas 1, (A) et (B) sont des moyens sûrs d’atteindre le résultat souhaité. Dans le cas où (2), (A) et (C) sont sûrs, mais (B) ne le serait pas [cela ne poserait pas de problèmes immédiats, mais puisque George supposerait qu'il est propriétaire du réseau, il supposerait que pourrait changer le tableau à volonté]. Dans le cas (3), les choix (A) et (B) briseraient les vues extérieures et seul le choix (C) est correct. Ainsi, savoir comment modifier la séquence de caractères encapsulée par le champ nécessite de savoir de quel type de champ il s'agit.

Si, au lieu d'utiliser un champ de type char[], qui encapsule une séquence de caractères potentiellement mutable, le code utilise le type String, qui encapsule une séquence de caractères immuable, tous les problèmes ci-dessus disparaissent. Tous les champs de type Stringencapsulent une séquence de caractères à l'aide d'un objet partageable qui ne changera jamais. Par conséquent, si un champ de typeStringencapsule "le vent", le seul moyen de le faire encapsuler "baguette magique" est de lui faire identifier un objet différent - celui qui contient "baguette magique". Dans les cas où le code contient la seule référence à l'objet, la mutation de l'objet peut être plus efficace que la création d'une nouvelle, mais chaque fois qu'une classe est mutable, il est nécessaire de distinguer les différents moyens par lesquels elle peut encapsuler une valeur. Personnellement, j’estime que Apps Hungarian aurait dû être utilisé à cet effet (je considérerais que les quatre utilisations de char[]sont des types distincts du point de vue sémantique, même si le système de types les considère identiques, ce qui correspond exactement au type de situation où Apps Hungarian brille). Pour éviter de telles ambiguïtés, le plus simple n’était pas de concevoir des types immuables qui encapsulent les valeurs d’une manière ou d’une autre.

supercat
la source
Cela semble une réponse raisonnable, mais est un peu difficile à lire et à comprendre.
Den
1

Il y a quelques exemples intéressants ici, mais je voulais intervenir avec des exemples personnels où l'immutabilité a aidé énormément. Dans mon cas, j'ai commencé par concevoir une structure de données concurrente immuable, principalement dans l'espoir de pouvoir exécuter du code en toute confiance en parallèle avec des lectures et des écritures qui se chevauchent, sans avoir à se soucier des conditions de concurrence. Il y a eu une discussion que John Carmack a donnée m'a inspiré à le faire lorsqu'il a parlé d'une telle idée. C'est une structure assez basique et triviale à implémenter comme ceci:

entrez la description de l'image ici

Bien sûr, avec quelques cloches et des sifflets de plus, c'est comme être capable de supprimer des éléments en temps constant et de laisser des trous récupérables derrière et de laisser les blocs se dérégler s'ils deviennent vides et potentiellement libérés pour une instance immuable donnée. Mais fondamentalement, pour modifier la structure, vous modifiez une version "transitoire" et vous engagez de manière atomique les modifications que vous y avez apportées pour obtenir une nouvelle copie immuable qui ne touche pas l'ancienne, la nouvelle version ne créant que de nouvelles copies des blocs doivent être rendus uniques tout en faisant une copie superficielle et en comptant les autres.

Cependant, je ne trouve pas queutile pour le multithreading. Après tout, il y a toujours le problème conceptuel selon lequel, par exemple, un système physique applique la physique simultanément alors qu'un joueur essaie de déplacer des éléments dans un monde. Avec quelle copie immuable des données transformées allez-vous, celle que le joueur a transformée ou celle que le système physique a transformée? Donc, je n'ai pas vraiment trouvé de solution simple et agréable à ce problème conceptuel de base, si ce n'est d'avoir des structures de données modifiables qui se verrouillent de manière plus intelligente et découragent les lectures et les écritures qui se chevauchent dans les mêmes sections du tampon pour éviter les problèmes de threads. C'est quelque chose que John Carmack semble avoir probablement compris comment résoudre ses problèmes. au moins, il en parle comme s'il pouvait presque trouver une solution sans ouvrir une voiture de vers. Je ne suis pas allé aussi loin que lui à cet égard. Tout ce que je peux voir, ce sont des questions de conception sans fin si j'essayais de tout mettre en parallèle avec Immutables. J'aimerais pouvoir passer une journée à le cueillir dans la tête, car la plupart de mes efforts ont débuté avec ces idées qu'il a proposées.

Néanmoins, j'ai trouvé une valeur énorme de cette structure de données immuable dans d'autres domaines. Je l'utilise même maintenant pour stocker des images vraiment bizarres et rendant l'accès aléatoire nécessitant quelques instructions supplémentaires (décalage droit et binaire andavec couche de pointeur indirectionnel), mais je traiterai des avantages ci-dessous.

Annuler le système

L'un des endroits les plus immédiats dont j'ai tiré profit était le système d'annulation. Le code de système d'annulation était l'une des choses les plus sujettes aux erreurs de ma région (secteur des effets visuels), et pas seulement dans les produits sur lesquels j'ai travaillé, mais aussi dans les produits concurrents (leurs systèmes d'annulation étaient également irréguliers) car il y avait tellement de solutions différentes. types de données à craindre d'annuler et de refaire correctement (système de propriétés, changements de données de maillage, changements de shader qui n'étaient pas basés sur des propriétés, comme l'échange entre eux, changements de hiérarchie de scènes comme changer le parent d'un enfant, changements d'image / texture, etc. etc. etc.).

Ainsi, la quantité de code d'annulation nécessaire était énorme, rivalisant souvent avec la quantité de code implémentant le système pour lequel le système d'annulation devait enregistrer les changements d'état. En m'appuyant sur cette structure de données, j'ai pu réduire le système d'annulation à ceci:

on user operation:
    copy entire application state to undo entry
    perform operation

on undo/redo:
    swap application state with undo entry

Normalement, le code ci-dessus serait extrêmement inefficace lorsque vos données de scène couvrent des gigaoctets pour le copier intégralement. Mais cette structure de données ne copie que de manière superficielle les choses qui n'ont pas changé, et elle permet en fait de stocker, de manière peu coûteuse, une copie immuable de l'état complet de l'application. Je peux donc maintenant mettre en œuvre des systèmes d'annulation aussi facilement que le code ci-dessus et me concentrer sur l'utilisation de cette structure de données immuable pour rendre la copie des parties non modifiées de l'état de l'application moins chère, moins chère et moins chère. Depuis que j'ai commencé à utiliser cette structure de données, tous mes projets personnels ont des systèmes d'annulation utilisant uniquement ce modèle simple.

Maintenant, il y a encore des frais généraux ici. La dernière fois que j’ai mesuré que c’était environ 10 kilo-octets, il suffit de copier superficiellement l’état complet de l’application sans y apporter de modification (ceci est indépendant de la complexité de la scène car celle-ci est organisée dans une hiérarchie. Ainsi, rien ne change en dessous de la racine, est copié peu profond sans avoir à descendre dans les enfants). C’est loin de 0 octets, ce qui serait nécessaire pour un système d'annulation stockant uniquement des deltas. Toutefois, avec 10 kilo-octets de temps supplémentaire d'annulation par opération, il ne reste qu'un mégaoctet pour 100 opérations utilisateur. De plus, je pourrais encore potentiellement le réduire davantage si nécessaire.

Sécurité d'exception

La sécurité des exceptions avec une application complexe n’est pas une mince affaire. Cependant, lorsque l'état de votre application est immuable et que vous utilisez uniquement des objets transitoires pour tenter de valider des transactions de changement atomique, il est intrinsèquement protégé contre les exceptions, car si une partie du code est jetée, le composant transitoire est éliminé avant de donner une nouvelle copie immuable. . Cela banalise donc l’une des choses les plus difficiles que j’ai toujours trouvées à réussir dans une base de code C ++ complexe.

Trop de gens utilisent souvent simplement des ressources conformes à RAII en C ++ et pensent que cela suffit pour être à l'abri des exceptions. Ce n’est souvent pas le cas, puisqu’une fonction peut généralement causer des effets secondaires aux États situés au-delà de ceux situés localement. Dans ces cas, vous devez généralement commencer à traiter avec des gardes-objectifs et une logique de retour en arrière sophistiquée. Cette structure de données a été conçue pour que je n'ai souvent pas à m'en préoccuper, car les fonctions ne causent pas d'effets secondaires. Ils renvoient des copies immuables transformées de l'état de l'application au lieu de transformer l'état de l'application.

Montage non destructif

entrez la description de l'image ici

L’édition non destructive consiste essentiellement à superposer, empiler et relier des opérations sans toucher aux données de l’utilisateur original (il suffit de saisir des données et d’en sortir des données sans toucher à l’entrée). Il est généralement trivial de le mettre en œuvre avec une simple application d’image, telle que Photoshop, et cette structure de données ne bénéficiera peut-être pas beaucoup de cette opération, car de nombreuses opérations pourraient vouloir transformer chaque pixel de l’ensemble de l’image.

Cependant, avec l'édition de maillage non destructif, par exemple, de nombreuses opérations ne veulent souvent transformer qu'une partie du maillage. Une opération peut simplement vouloir déplacer des sommets ici. Un autre pourrait simplement vouloir subdiviser certains polygones à cet endroit. Ici, la structure de données immuable aide beaucoup à éviter la nécessité de faire une copie complète de tout le maillage pour renvoyer une nouvelle version du maillage avec une petite partie de celle-ci modifiée.

Minimiser les effets secondaires

Avec ces structures en main, il est également facile d’écrire des fonctions qui minimisent les effets secondaires sans encourir d’énormes pertes de performances. Je me suis retrouvé à écrire de plus en plus de fonctions qui ne font que restituer des structures de données immuables par valeur ces derniers temps sans que cela n'entraîne d'effets secondaires, même lorsque cela semble un peu inutile.

Par exemple, la tentation de transformer un groupe de positions consiste généralement à accepter une matrice et une liste d'objets et à les transformer de manière mutable. Ces jours-ci, je me retrouve à renvoyer une nouvelle liste d'objets.

Lorsque votre système contient davantage de fonctions comme celle-ci ne causant pas d'effets secondaires, il est nettement plus facile de raisonner au sujet de son exactitude, ainsi que de le tester.

Les avantages des copies bon marché

Donc de toute façon, ce sont les domaines dans lesquels j'ai trouvé le plus d'utilisation de structures de données immuables (ou de structures de données persistantes). Au départ, j’ai eu un peu trop de zèle et j’ai créé un arbre immuable, une liste chaînée immuable et une table de hachage immuable, mais au fil du temps, j’ai rarement trouvé autant d’utilisation pour ceux-ci. Dans le diagramme ci-dessus, j'ai principalement trouvé l'utilisation la plus fréquente du conteneur en forme de tableau immuable et volumineux.

Il me reste également beaucoup de code travaillant avec des mutables (la pratique le trouve moins nécessaire pour le code de bas niveau), mais l’état principal de l’application est une hiérarchie immuable, descendant d’une scène immuable à des composants immuables qui la composent. Certains des composants les moins chers sont toujours intégralement copiés, mais les plus chers, tels que les maillages et les images, utilisent la structure immuable pour autoriser les copies partielles peu coûteuses des seules pièces à transformer.


la source
0

Il y a déjà beaucoup de bonnes réponses. Ceci est juste une information supplémentaire quelque peu spécifique à .NET. Je cherchais dans d'anciens articles de blog .NET et j'ai trouvé un bon résumé des avantages du point de vue des développeurs de Microsoft Immutable Collections:

  1. Sémantique des instantanés, vous permettant de partager vos collections de manière à ce que le destinataire puisse ne jamais changer.

  2. Sécurité implicite des threads dans les applications multithreads (aucun verrou nécessaire pour accéder aux collections).

  3. Chaque fois que vous avez un membre de la classe qui accepte ou retourne un type de collection et que vous souhaitez inclure la sémantique en lecture seule dans le contrat.

  4. Programmation fonctionnelle conviviale.

  5. Autoriser la modification d'une collection pendant l'énumération, tout en veillant à ce que la collection d'origine ne change pas.

  6. Ils implémentent les mêmes interfaces IReadOnly * que votre code traite déjà pour faciliter la migration.

Si quelqu'un vous remet une ReadOnlyCollection, un IReadOnlyList ou un IEnumerable, la seule garantie est que vous ne pouvez pas modifier les données - rien ne garantit que la personne qui vous a remis la collection ne le modifiera pas. Pourtant, vous avez souvent besoin de savoir que cela ne changera pas. Ces types n'offrent pas d'événements pour vous avertir lorsque leur contenu change, et s'ils changent, cela peut-il se produire sur un autre thread, éventuellement pendant l'énumération de son contenu? Un tel comportement entraînerait une corruption de données et / ou des exceptions aléatoires dans votre application.

Tanière
la source