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?
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."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).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.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.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.
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.
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.
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.
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.
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.
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.
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.
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
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).
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:
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
.
Les chaînes immuables empêchent également les problèmes liés à la simultanéité.
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?