web-dev-qa-db-fra.com

Pourquoi les structures C # sont-elles immuables?

J'étais juste curieux de savoir pourquoi les structures, les chaînes, etc. sont immuables? Quelle est la raison de les rendre immuables et le reste des objets comme mutable. Quelles sont les choses qui sont considérées comme rendant un objet immuable?

Y a-t-il une différence dans la façon dont la mémoire est allouée et désallouée pour les objets mutables et immuables?

57
Sandeep

Si ce sujet vous intéresse, j'ai un certain nombre d'articles sur la programmation immuable à http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

J'étais juste curieux de savoir pourquoi les structures, les chaînes, etc. sont immuables?

Les structures et les classes ne sont pas immuables par défaut, bien qu'il soit recommandé de rendre les structures immuables. J'aime aussi les cours immuables.

Les cordes sont immuables.

Quelle est la raison de les rendre immuables et le reste des objets comme mutable.

Raisons de rendre tous les types immuables:

  • Il est plus facile de raisonner sur des objets qui ne changent pas. Si j'ai une file d'attente avec trois éléments, je sais qu'elle n'est pas vide maintenant, elle n'était pas vide il y a cinq minutes, elle ne sera pas vide à l'avenir. C'est immuable! Une fois que je connais un fait à ce sujet, je peux l'utiliser pour toujours. Les faits concernant les objets immuables ne deviennent pas périmés.

  • Un cas particulier du premier point: les objets immuables sont beaucoup plus faciles à rendre threadsafe. La plupart des problèmes de sécurité des threads sont dus aux écritures sur un thread et aux lectures sur un autre; les objets immuables n'ont pas d'écritures.

  • Les objets immuables peuvent être démontés et réutilisés. Par exemple, si vous avez un arbre binaire immuable, vous pouvez utiliser ses sous-arbres gauche et droit comme sous-arbres d'un arbre différent sans vous en soucier. Dans une structure modifiable, vous finissez généralement par faire des copies de données pour les réutiliser, car vous ne voulez pas que des modifications apportées à un objet logique en affectent un autre. Cela peut économiser beaucoup de temps et de mémoire.

Raisons de rendre les structures immuables

Il y a beaucoup de raisons de rendre les structures immuables. En voici un.

Les structures sont copiées par valeur et non par référence. Il est facile de traiter accidentellement une structure comme étant copiée par référence. Par exemple:

void M()
{
    S s = whatever;
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
    Console.WriteLine(s.Foo);
    ...
}

Maintenant, vous voulez refactoriser une partie de ce code dans une méthode d'assistance:

void Helper(S s)
{
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
}

FAUX! Cela devrait être (ref S s) - si vous ne le faites pas, la mutation se produira sur une copie de s. Si vous n'autorisez pas les mutations en premier lieu, toutes ces sortes de problèmes disparaissent.

Raisons de rendre les cordes immuables

Rappelez-vous mon premier point sur les faits concernant les structures immuables qui restent des faits?

Supposons que la chaîne soit modifiable:

public static File OpenFile(string filename)
{
    if (!HasPermission(filename)) throw new SecurityException();
    return InternalOpenFile(filename);
}

Que faire si l'appelant hostile mute le nom de fichier après le contrôle de sécurité et avant le le fichier est ouvert? Le code vient d'ouvrir un fichier auquel ils ne sont peut-être pas autorisés!

Encore une fois, il est difficile de raisonner sur les données mutables. Vous voulez que le fait "cet appelant soit autorisé à voir le fichier décrit par cette chaîne" soit vrai pour toujours , pas jusqu'à ce qu'une mutation se produise . Avec des chaînes mutables, pour écrire du code sécurisé, nous devions constamment faire des copies de données que nous savons ne pas changer.

Quelles sont les choses qui sont considérées comme rendant un objet immuable?

Le type représente-t-il logiquement quelque chose qui est une valeur "éternelle"? Le nombre 12 est le nombre 12; ça ne change pas. Les entiers doivent être immuables. Le point (10, 30) est le point (10, 30); ça ne change pas. Les points doivent être immuables. La chaîne "abc" est la chaîne "abc"; ça ne change pas. Les chaînes doivent être immuables. La liste (10, 20, 30) ne change pas. Etc.

Parfois, le type représente des choses qui changent. Le nom de famille de Mary Smith est Smith, mais demain elle pourrait être Mary Jones. Ou Miss Smith aujourd'hui pourrait être le docteur Smith demain. L'étranger a maintenant cinquante points de vie mais en a dix après avoir été touché par le faisceau laser. Certaines choses sont mieux représentées comme des mutations.

Y a-t-il une différence dans la façon dont la mémoire est allouée et désallouée pour les objets mutables et immuables?

Pas comme tel. Comme je l'ai mentionné auparavant, l'une des bonnes choses à propos des valeurs immuables est que vous pouvez en réutiliser certaines parties sans faire de copies. Donc, dans ce sens, l'allocation de mémoire peut être très différente.

115
Eric Lippert

Les structures non ... c'est pourquoi les structures mutables sont mauvaises.

La création de structures mutables peut conduire à toutes sortes de comportements étranges dans votre application et, par conséquent, elles sont considérées comme une très mauvaise idée (car elles ressemblent à un type de référence mais sont en fait un type de valeur et seront copiées chaque fois que vous passerez les autour).

Les cordes, en revanche, le sont. Cela les rend intrinsèquement thread-safe et permet des optimisations via l'internement de chaînes. Si vous devez construire une chaîne compliquée à la volée, vous pouvez utiliser StringBuilder.

9
Justin Niessner

Les concepts de mutabilité et d'immutabilité ont des significations différentes lorsqu'ils sont appliqués aux structures et aux classes. Un aspect clé (souvent, la principale faiblesse) des classes mutables est si Foo a un champ Bar de type List<Integer>, qui contient une référence à une liste contenant (1,2,3), un autre code qui a une référence à cette même liste pourrait la modifier, de telle sorte que Bar contient une référence à une liste contenant (4 , 5,6), même si cet autre code n'a aucun accès à Bar. En revanche, si Foo avait un champ Biz de type System.Drawing.Point, la seule façon dont quelque chose pourrait modifier n'importe quel aspect de Biz serait d'avoir un accès en écriture à ce champ .

Les champs (publics et privés) d'une structure peuvent être mutés par n'importe quel code qui peut muter l'emplacement de stockage dans lequel la structure est stockée, et ne peuvent pas être mutés par un code qui ne peut pas muter l'emplacement de stockage où elle est stockée. Si toutes les informations encapsulées dans une structure sont conservées dans ses champs, une telle structure peut effectivement combiner le contrôle d'un type immuable avec la commodité d'un type mutable, sauf si la structure est codée de manière à supprimer cette commodité ( une habitude que, malheureusement, certains programmeurs Microsoft recommandent).

Le "problème" avec les structures est que lorsqu'une méthode (y compris une implémentation de propriété) est invoquée sur une structure dans un contexte en lecture seule (ou un emplacement immuable), le système copie la structure, exécute la méthode sur la copie temporaire et en silence jette le résultat. Ce comportement a conduit les programmeurs à émettre l'idée malheureuse que la façon d'éviter les problèmes avec les méthodes de mutation est d'avoir de nombreuses structures interdisant les mises à jour par morceaux, alors que les problèmes auraient pu être mieux évités en en remplaçant simplement les propriétés avec des champs exposés .

Soit dit en passant, certaines personnes se plaignent que lorsqu'une propriété de classe renvoie une structure commodément mutable, les modifications apportées à la structure n'affectent pas la classe dont elle provient. Je suppose que c'est une bonne chose - le fait que l'élément retourné est une structure rend le comportement clair (surtout s'il s'agit d'une structure à champ exposé). Comparez un extrait à l'aide d'une structure et d'une propriété hypothétiques sur Drawing.Matrix avec une utilisant une propriété réelle sur cette classe telle qu'implémentée par Microsoft:

 // Structure hypothétique 
 Structure publique {
 Public float xx, xy, yx, yy, dx, dy; 
} Transform2d; 
 
 // Propriété hypothétique de "System.Drawing.Drawing2d.Matrix" 
 Public Transform2d Transform {get;} 
 
 // Propriété réelle de "System.Drawing. Drawing2d.Matrix "
 Public float [] Elements {get; } 
 
 // Code utilisant une structure hypothétique 
 Transform2d myTransform = myMatrix.Transform; 
 MyTransform.dx + = 20; 
 ... autre code utilisant myTransform 
 
 // Code utilisant la propriété Microsoft réelle 
 float [] myArray = myMatrix.Elements; 
 myArray [4] + = 20; 
 ... autre code utilisant myArray 

En regardant la propriété Microsoft réelle, existe-t-il un moyen de savoir si l'écriture dans myArray[4] affectera myMatrix? Même en regardant la page http://msdn.Microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx est-il possible de le savoir? Si la propriété avait été écrite en utilisant l'équivalent basé sur une structure, il n'y aurait pas de confusion; la propriété qui retourne la structure ne retournerait ni plus ni moins que la valeur actuelle de six nombres. En changeant myTransform.dx ne serait ni plus ni moins qu'une écriture dans une variable à virgule flottante qui n'était attachée à rien d'autre. Quiconque n'aime pas le fait que changer myTransform.dx n'affecte pas myMatrix devrait être aussi ennuyé que l'écriture myArray[4] n'affecte pas non plus myMatrix, sauf que l'indépendance de myMatrix et myTransform est apparente, tandis que l'indépendance de myMatrix et myArray n'est pas.

4
supercat

Un type struct n'est pas immuable. Oui, les cordes le sont. Rendre votre propre type immuable est facile, ne fournissez simplement pas de constructeur par défaut, rendez tous les champs privés et ne définissez aucune méthode ou propriété qui modifie une valeur de champ. Avoir une méthode qui devrait muter l'objet renvoyer un nouvel objet à la place. Il y a un angle de gestion de la mémoire, vous avez tendance à créer beaucoup de copies et de déchets.

2
Hans Passant

Les structures peuvent être mutables, mais c'est une mauvaise idée car elles ont une sémantique de copie. Si vous apportez une modification à une structure, vous pouvez en fait modifier une copie. Il est très difficile de suivre exactement ce qui a été changé.

Les structures mutables engendrent des erreurs.

1
Craig Gidney