web-dev-qa-db-fra.com

Pourquoi .NET String est-il immuable?

Comme nous le savons tous, String est immuable. Quelles sont les raisons pour lesquelles String est immuable et l'introduction de la classe StringBuilder en tant que mutable?

176
Nirajan Singh
  1. Les instances de types immuables sont intrinsèquement thread-safe, puisqu'un thread ne peut pas le modifier, le risque qu'un thread le modifie d'une manière qui interfère avec un autre est supprimé (la référence elle-même est différente).
  2. De même, le fait que les alias ne puissent pas produire de modifications (si x et y se rapportent tous deux au même objet, une modification de x entraîne une modification de y) permet une optimisation considérable du compilateur.
  3. Des optimisations d'économie de mémoire sont également possibles. Internation et atomisation sont les exemples les plus évidents, bien que nous puissions faire d’autres versions du même principe. Une fois, j’ai réalisé une économie de mémoire d’environ un demi-Go en comparant des objets immuables et en remplaçant les références par des doublons afin qu’ils pointent tous vers la même instance (fastidieux, mais une minute de démarrage supplémentaire pour économiser une énorme quantité de mémoire était un processus fastidieux. performance gagnante dans le cas en question). Avec des objets mutables qui ne peuvent pas être faits.
  4. En transmettant un type immuable en tant que méthode à un paramètre, aucun effet secondaire ne peut provenir de out ou ref (car cela change la référence, pas l'objet). Un programmeur sait donc que si string x = "abc" Au début d'une méthode et que cela ne change pas dans le corps de la méthode, alors x == "abc" À la fin de la méthode.
  5. Conceptuellement, la sémantique s'apparente davantage à des types de valeur; en particulier, l'égalité est basée sur l'état plutôt que sur l'identité. Cela signifie que "abc" == "ab" + "c". Bien que cela ne nécessite pas l'immuabilité, le fait qu'une référence à une telle chaîne soit toujours égale à "abc" tout au long de sa vie (ce qui nécessite l'immuabilité) en fait des utilisations où le maintien de l'égalité avec les valeurs précédentes est essentiel, ce qui facilite grandement la correction. of (les chaînes sont en effet couramment utilisées comme clés).
  6. Conceptuellement, il peut être plus logique d'être immuable. Si nous ajoutons un mois à Noël, nous n’avons pas changé de Noël, nous avons établi une nouvelle date à la fin janvier. Il est donc logique que Christmas.AddMonths(1) produise un nouveau DateTime plutôt que de modifier un mutable. (Un autre exemple, si, en tant qu'objet mutable, je change de nom, ce qui a changé est le nom que j'utilise, "Jon" reste immuable et les autres Jons ne seront pas affectés.
  7. La copie est simple et rapide, pour créer un clone seulement return this. Comme la copie ne peut de toute façon pas être modifiée, prétendre que quelque chose est sa propre copie est sûr.
  8. [Modifier, j'avais oublié celui-ci]. L'état interne peut être partagé en toute sécurité entre les objets. Par exemple, si vous implémentiez une liste qui était sauvegardée par un tableau, un index de début et un nombre, la partie la plus chère de la création d'une sous-plage serait la copie des objets. Cependant, s'il était immuable, l'objet de la sous-plage pourrait référencer le même tableau, seul l'index de début et le nombre devant changer, avec un changement considérable du temps de construction de très.

En tout, pour les objets dont le but n'est pas de changer, il peut y avoir de nombreux avantages à être immuable. Le principal inconvénient est de nécessiter des constructions supplémentaires, même si cela est souvent surestimé (rappelez-vous que vous devez faire plusieurs ajouts avant que StringBuilder ne devienne plus efficace que la série équivalente de concaténations, avec leur construction inhérente).

Il serait désavantageux que la mutabilité fasse partie du but d'un objet (qui voudrait être modélisé par un objet Employee dont le salaire ne pourrait jamais changer), même si cela peut parfois être utile (dans de nombreux sites Web et autres apatrides). les applications, les opérations de lecture de code sont différentes de celles de mise à jour, et utiliser des objets différents peut être naturel - je ne rendrais pas un objet immuable et ne forcerais ce motif, mais si j'avais déjà ce motif, je pourrais créer mes objets "en lecture" immuable pour le gain de performance et d’exactitude).

La copie sur écriture est un terrain d'entente. Ici, la classe "réelle" contient une référence à une classe "d'état". Les classes d'état sont partagées lors des opérations de copie, mais si vous modifiez l'état, une nouvelle copie de la classe d'état est créée. Ceci est plus souvent utilisé avec C++ que C #, raison pour laquelle std: string profite de certains des avantages des types immuables, mais pas tous, tout en restant modifiable.

232
Jon Hanna

Rendre les chaînes immuables présente de nombreux avantages. Il offre une sécurité de fil automatique et permet aux chaînes de se comporter comme un type intrinsèque de manière simple et efficace. Il permet également une efficacité accrue au moment de l'exécution (par exemple, un internage efficace des chaînes afin de réduire l'utilisation des ressources) et offre des avantages considérables en matière de sécurité, car il est impossible pour un appel d'API tiers de modifier vos chaînes.

StringBuilder a été ajouté afin de remédier au principal inconvénient des chaînes immuables: la construction à l'exécution de types immuables provoque beaucoup de pression sur le GC et est par conséquent lente. En faisant une classe explicite et mutable pour gérer cela, ce problème est résolu sans ajouter de complication inutile à la classe string.

74
Reed Copsey

Les cordes ne sont pas vraiment immuables. Ils sont juste immuables publiquement. Cela signifie que vous ne pouvez pas les modifier à partir de leur interface publique. Mais à l'intérieur, ils sont réellement mutables.

Si vous ne me croyez pas, regardez le String.Concat définition utilisant réflecteur . Les dernières lignes sont ...

int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;

Comme vous pouvez le voir, FastAllocateString renvoie une chaîne vide mais allouée, qui est ensuite modifiée par FillStringChecked.

En fait, le FastAllocateString est une méthode externe et le FillStringChecked est dangereux, de sorte qu'il utilise des pointeurs pour copier les octets.

Peut-être y at-il de meilleurs exemples, mais c’est celui que j’ai trouvé jusqu’à présent.

21
Carlos Muñoz

la gestion des chaînes est un processus coûteux. garder les chaînes immuables permet aux chaînes répétées d'être réutilisées plutôt que recréées.

13
kolosy

Pourquoi les types de chaînes sont-ils immuables en C #

String est un type de référence, il n'est donc jamais copié, mais transmis par référence. Comparez cela à l'objet std :: string C++ (qui n'est pas immuable), qui est passé par valeur. Cela signifie que si vous voulez utiliser une chaîne comme clé dans une table de hachage, tout va bien en C++, car C++ copie la chaîne pour stocker la clé dans la table de hachage (en fait std :: hash_map, mais quand même) pour une comparaison ultérieure. . Ainsi, même si vous modifiez ultérieurement l'instance std :: string, tout va bien. Mais en .Net, lorsque vous utilisez une chaîne dans une table de hachage, il enregistre une référence à cette instance. Supposons maintenant un instant que les chaînes ne sont pas immuables et voyons ce qui se passe: 1. Quelqu'un insère une valeur x avec la clé "hello" dans une table de hachage. 2. La table de hachage calcule la valeur de hachage pour la chaîne et place une référence à la chaîne et la valeur x dans le compartiment approprié. 3. L'utilisateur modifie l'instance de String en "bye". 4. Maintenant, quelqu'un veut la valeur de la table de hachage associée à "hello". Il finit par regarder dans le bon compartiment, mais lorsque vous comparez les chaînes, il indique "bye"! = "Hello", aucune valeur n'est donc renvoyée. 5. Peut-être que quelqu'un veut la valeur "au revoir"? "bye" a probablement un hachage différent, donc la hashtable devrait être dans un seau différent. Aucune clé "bye" dans ce seau, notre entrée n'est toujours pas trouvée.

Rendre les chaînes immuables signifie que l'étape 3 est impossible. Si quelqu'un modifie la chaîne, il crée un nouvel objet chaîne en laissant l'ancien. Ce qui signifie que la clé dans la table de hachage est toujours "bonjour", et donc toujours correcte.

Ainsi, probablement, entre autres choses, les chaînes immuables sont un moyen de permettre aux chaînes passées par référence d'être utilisées comme clés dans un hashtable ou un objet dictionnaire similaire.

13
NebuSoft

Vous n'avez jamais à copier de manière défensive des données immuables. Malgré le fait que vous devez le copier pour le muter, la possibilité de vous aliaser librement et de ne jamais avoir à vous soucier des conséquences inattendues de ce pseudonyme peut conduire à de meilleures performances en raison du manque de copie défensive.

5
dsimcha

Pour ajouter ceci, une vue souvent oubliée est celle de la sécurité, imaginez ce scénario si les chaînes étaient mutables:

string dir = "C:\SomePlainFolder";

//Kick off another thread
GetDirectoryContents(dir);

void GetDirectoryContents(string directory)
{
  if(HasAccess(directory) {
    //Here the other thread changed the string to "C:\AllYourPasswords\"
    return Contents(directory);
  }
  return null;
}

Vous voyez à quel point cela pourrait être très très grave si vous pouviez modifier les chaînes une fois qu'elles étaient passées.

5
Nick Craver

Les chaînes sont transmises en tant que types de référence dans .NET.

Les types de référence placent un pointeur sur la pile, sur l'instance réelle qui réside sur le segment de mémoire géré. Cela diffère des types Value, qui conservent toute leur instance sur la pile.

Lorsqu'un type de valeur est passé en tant que paramètre, le moteur d'exécution crée une copie de la valeur sur la pile et la transmet à une méthode. C'est pourquoi les entiers doivent être passés avec un mot clé 'ref' pour renvoyer une valeur mise à jour.

Lorsqu'un type de référence est passé, le moteur d'exécution crée une copie du pointeur sur la pile. Ce pointeur copié pointe toujours vers l'instance d'origine du type de référence.

Le type de chaîne a un opérateur surchargé = qui crée une copie de lui-même, au lieu d'une copie du pointeur, le faisant se comporter plutôt comme un type de valeur. Cependant, si seul le pointeur était copié, une deuxième opération de chaîne pourrait écraser par inadvertance la valeur d'un membre privé d'une autre classe, ce qui provoquerait des résultats plutôt désagréables.

Comme d'autres publications l'ont mentionné, la classe StringBuilder permet la création de chaînes sans surcharge du GC.

5
Kevin McKelvin

Les chaînes et autres objets concrets sont généralement exprimés en tant qu'objets immuables pour améliorer la lisibilité et l'efficacité d'exécution. La sécurité en est un autre, un processus ne peut pas changer votre chaîne et injecter du code dans la chaîne

3
SQLMenace

Imaginez que vous passiez une chaîne mutable à une fonction mais ne vous attendiez pas à ce qu'elle soit modifiée. Alors que se passe-t-il si la fonction change cette chaîne? En C++, par exemple, vous pouvez simplement faire appel par valeur (différence entre std::string et std::string& paramètre), mais en C #, il s’agit de références, donc si vous passiez des chaînes mutables autour de chaque fonction, cela pourrait la changer et provoquer des effets secondaires inattendus.

Ce n'est qu'une des nombreuses raisons. La performance en est une autre (chaînes internes, par exemple).

3
AndiDog

Il existe cinq méthodes courantes par lesquelles des données de magasin de données de classe ne peuvent pas être modifiées en dehors du contrôle de la classe de stockage:

  1. En tant que primitives de type valeur
  2. En détenant une référence librement partageable à un objet de classe dont les propriétés d'intérêt sont toutes immuables
  3. En détenant une référence à un objet de classe mutable qui ne sera jamais exposé à quoi que ce soit qui pourrait muter les propriétés d'intérêt
  4. En tant que struct, "mutable" ou "immuable", tous les champs sont des types # 1- # 4 (et non # 5).
  5. En conservant la seule copie existante d'une référence à un objet dont les propriétés ne peuvent être mutées que via cette référence.

Comme les chaînes ont une longueur variable, elles ne peuvent pas être des primitives de type valeur ni leurs données de caractères ne peuvent être stockées dans une structure. Parmi les choix restants, le seul qui n'exigerait pas que les données de caractères des chaînes soit stockée dans une sorte d'objet immuable serait le n ° 5. Bien qu'il soit possible de concevoir un cadre autour de l'option n ° 5, ce choix nécessiterait que tout code souhaitant une copie d'une chaîne qui ne puisse pas être modifiée en dehors de son contrôle doive en faire une copie privée. Bien qu’il soit difficilement impossible de le faire, la quantité de code supplémentaire requise pour le faire et le temps de traitement supplémentaire nécessaire pour réaliser des copies défensives l'emporteraient de loin sur les légers avantages qui pourraient découler de string être mutable, surtout étant donné qu'il existe un type de chaîne mutable (System.Text.StringBuilder _ qui réalise 99% de ce qui pourrait être accompli avec un mutable string.

2
supercat

Les chaînes immuables empêchent également les problèmes liés à la simultanéité.

0
Ken Liu

Imaginez être un système d'exploitation utilisant une chaîne qu'un autre thread modifiait derrière votre dos. Comment pouvez-vous valider quoi que ce soit sans en faire une copie?

0
Eton B.