Une chaîne est un type de référence même si elle possède la plupart des caractéristiques d'un type de valeur, comme être immuable et avoir == surchargé pour comparer le texte plutôt que de s'assurer qu'elles référencent le même objet.
Pourquoi la chaîne n'est-elle pas simplement un type de valeur?
c#
string
clr
value-type
reference-type
Davy8
la source
la source
is
côté les tests), la réponse est probablement "pour des raisons historiques". Les performances de copie ne peuvent pas être la raison, car il n'est pas nécessaire de copier physiquement des objets immuables. Il est maintenant impossible de changer sans casser le code qui utilise réellement desis
contrôles (ou des contraintes similaires).std::string
comporter comme une collection est une vieille erreur qui ne peut pas être corrigée maintenant.Réponses:
Les chaînes ne sont pas des types de valeur car elles peuvent être énormes et doivent être stockées sur le tas. Les types de valeurs sont (dans toutes les implémentations du CLR pour l'instant) stockés sur la pile. L'allocation de piles de chaînes casserait toutes sortes de choses: la pile n'est que de 1 Mo pour 32 bits et 4 Mo pour 64 bits, vous devez encadrer chaque chaîne, ce qui entraînerait une pénalité de copie, vous ne pourriez pas interner des chaînes et l'utilisation de la mémoire serait ballon, etc ...
(Modifier: Ajout d'une clarification sur le stockage de type valeur étant un détail d'implémentation, ce qui conduit à cette situation où nous avons un type avec une sémantique de valeur non héritée de System.ValueType. Merci Ben.)
la source
String
n'est pas de taille variable. Lorsque vous y ajoutez, vous créez en fait un autreString
objet, en lui allouant une nouvelle mémoire.Int32
vaut toujours 4 octets, donc le compilateur alloue 4 octets chaque fois que vous définissez une variable de chaîne. Quelle quantité de mémoire le compilateur doit-il allouer lorsqu'il rencontre uneint
variable (s'il s'agissait d'un type de valeur)? Sachez que la valeur n'a pas encore été affectée à ce moment.Int32
vaut toujours 4 octets, donc le compilateur alloue 4 octets chaque fois que vous définissez uneint
variable. Quelle quantité de mémoire le compilateur doit-il allouer lorsqu'il rencontre unestring
variable (s'il s'agissait d'un type de valeur)? Sachez que la valeur n'a pas encore été affectée à ce moment.Ce n'est pas un type de valeur car les performances (espace et temps!) Seraient terribles s'il s'agissait d'un type de valeur et sa valeur devait être copiée à chaque fois qu'elle était transmise et renvoyée par des méthodes, etc.
Il a une sémantique de valeur pour garder le monde sain d'esprit. Pouvez-vous imaginer à quel point il serait difficile de coder si
prêt
b
à êtrefalse
? Imaginez à quel point le codage serait difficile pour n'importe quelle application.la source
new String("foo");
et un autrenew String("foo")
peut évaluer dans la même référence, ce qui n'est pas ce que vous attendez d'unnew
opérateur. (Ou pouvez-vous me dire un cas où je voudrais comparer les références?)ReferenceEquals(x, y)
est un test rapide et vous pouvez retourner immédiatement 0, et lorsqu'il est mélangé avec votre test nul n'ajoute même plus de travail.string
pourrait se comporter comme une chaîne vide (comme c'était le cas dans les systèmes pré -.net) plutôt que comme une référence nulle. En fait, ma propre préférence serait d'avoir un type de valeurString
qui contiendrait un type de référenceNullableString
, le premier ayant une valeur par défaut équivalente àString.Empty
et le second ayant un défaut denull
et avec des règles spéciales de boxe / unboxing (telles que boxer un défaut- valeurNullableString
donnerait une référence àString.Empty
).La distinction entre les types de référence et les types de valeur est fondamentalement un compromis de performance dans la conception du langage. Les types de référence ont des frais généraux sur la construction et la destruction et la collecte des ordures, car ils sont créés sur le tas. Les types de valeurs, d'autre part, ont des frais généraux sur les appels de méthode (si la taille des données est supérieure à un pointeur), car l'objet entier est copié plutôt qu'un simple pointeur. Étant donné que les chaînes peuvent être (et sont généralement) beaucoup plus grandes que la taille d'un pointeur, elles sont conçues comme types de référence. De plus, comme l'a souligné Servy, la taille d'un type de valeur doit être connue au moment de la compilation, ce qui n'est pas toujours le cas pour les chaînes.
La question de la mutabilité est une question distincte. Les types de référence et les types de valeur peuvent être mutables ou immuables. Les types de valeurs sont généralement immuables, car la sémantique des types de valeurs mutables peut prêter à confusion.
Les types de référence sont généralement modifiables, mais peuvent être conçus comme immuables si cela a du sens. Les chaînes sont définies comme immuables car elles permettent certaines optimisations. Par exemple, si le même littéral de chaîne se produit plusieurs fois dans le même programme (ce qui est assez courant), le compilateur peut réutiliser le même objet.
Alors pourquoi "==" est-il surchargé pour comparer les chaînes par texte? Parce que c'est la sémantique la plus utile. Si deux chaînes sont égales par du texte, elles peuvent ou non être la même référence d'objet en raison des optimisations. Donc, comparer des références est assez inutile, alors que comparer du texte est presque toujours ce que vous voulez.
De manière plus générale, Strings a ce qu'on appelle la sémantique des valeurs . Il s'agit d'un concept plus général que les types de valeur, qui est un détail d'implémentation spécifique à C #. Les types de valeur ont une sémantique de valeur, mais les types de référence peuvent également avoir une sémantique de valeur. Lorsqu'un type a une sémantique de valeur, vous ne pouvez pas vraiment dire si l'implémentation sous-jacente est un type de référence ou un type de valeur, vous pouvez donc considérer cela comme un détail d'implémentation.
la source
string
type devrait avoir un tampon de caractères d'une certaine taille fixe, ce qui serait à la fois restrictif et très inefficace.Il s'agit d'une réponse tardive à une vieille question, mais toutes les autres réponses ne sont pas pertinentes, à savoir que .NET n'avait pas de génériques avant .NET 2.0 en 2005.
String
est un type de référence au lieu d'un type de valeur car il était d'une importance cruciale pour Microsoft de s'assurer que les chaînes pouvaient être stockées de la manière la plus efficace dans des collections non génériques , telles queSystem.Collections.ArrayList
.Le stockage d'un type de valeur dans une collection non générique nécessite une conversion spéciale vers le type
object
appelé boxe. Lorsque le CLR met en boîte un type de valeur, il encapsule la valeur dans unSystem.Object
et le stocke sur le tas managé.La lecture de la valeur de la collection nécessite l'opération inverse qui est appelée unboxing.
La boxe et le déballage ont un coût non négligeable: la boxe nécessite une allocation supplémentaire, le déballage nécessite une vérification de type.
Certaines réponses prétendent incorrectement qui
string
n'auraient jamais pu être implémentées en tant que type de valeur car sa taille est variable. En fait, il est facile d'implémenter une chaîne en tant que structure de données de longueur fixe en utilisant une stratégie d'optimisation de petite chaîne: les chaînes seraient stockées en mémoire directement sous la forme d'une séquence de caractères Unicode, à l'exception des grandes chaînes qui seraient stockées en tant que pointeur vers un tampon externe. Les deux représentations peuvent être conçues pour avoir la même longueur fixe, c'est-à-dire la taille d'un pointeur.Si les génériques avaient existé dès le premier jour, je suppose que la chaîne comme type de valeur aurait probablement été une meilleure solution, avec une sémantique plus simple, une meilleure utilisation de la mémoire et une meilleure localité de cache. Un
List<string>
contenant uniquement de petites chaînes aurait pu être un seul bloc de mémoire contigu.la source
string
ne contient que sa taille et un pointeur vers lechar
tableau de toute façon, donc ce ne serait pas un "type de valeur énorme". Mais c'est une raison simple et pertinente pour cette décision de conception. Merci!Non seulement les chaînes sont des types de référence immuables. Délégués multi-cast aussi. C'est pourquoi il est sûr d'écrire
Je suppose que les chaînes sont immuables car c'est la méthode la plus sûre pour travailler avec elles et allouer de la mémoire. Pourquoi ce ne sont pas des types de valeur? Les auteurs précédents ont raison sur la taille de la pile, etc. J'ajouterais également que faire des chaînes un type de référence permet d'économiser sur la taille de l'assemblage lorsque vous utilisez la même chaîne constante dans le programme. Si vous définissez
Il est probable que les deux instances de la constante "ma chaîne" ne seront allouées qu'une seule fois dans votre assembly.
Si vous souhaitez gérer des chaînes comme le type de référence habituel, placez la chaîne dans un nouveau StringBuilder (chaîne (s)). Ou utilisez MemoryStreams.
Si vous devez créer une bibliothèque, où vous vous attendez à ce qu'une énorme chaîne soit passée dans vos fonctions, définissez un paramètre en tant que StringBuilder ou en tant que Stream.
la source
En outre, la façon dont les chaînes sont implémentées (différentes pour chaque plate-forme) et lorsque vous commencez à les assembler. Comme utiliser un
StringBuilder
. Il alloue un tampon pour vous copier, une fois que vous atteignez la fin, il vous alloue encore plus de mémoire, dans l'espoir que si vous faites une grande concaténation, les performances ne seront pas entravées.Peut-être que Jon Skeet peut aider ici?
la source
Il s'agit principalement d'un problème de performances.
Avoir des chaînes se comportent comme un type de valeur LIKE aide lors de l'écriture de code, mais le faire être un type de valeur ferait une énorme performance.
Pour un regard en profondeur, jetez un œil à un bel article sur les chaînes dans le framework .net.
la source
En termes très simples, toute valeur ayant une taille définie peut être traitée comme un type de valeur.
la source
Comment savoir si
string
c'est un type de référence? Je ne suis pas sûr que la façon dont il est mis en œuvre soit importante. Les chaînes en C # sont immuables précisément afin que vous n'ayez pas à vous soucier de ce problème.la source
En fait, les chaînes ont très peu de ressemblances avec les types de valeur. Pour commencer, tous les types de valeurs ne sont pas immuables, vous pouvez modifier la valeur d'un Int32 à votre guise et ce serait toujours la même adresse sur la pile.
Les chaînes sont immuables pour une très bonne raison, cela n'a rien à voir avec le fait qu'il s'agit d'un type de référence, mais a beaucoup à voir avec la gestion de la mémoire. Il est juste plus efficace de créer un nouvel objet lorsque la taille de la chaîne change que de déplacer des éléments sur le tas géré. Je pense que vous mélangez ensemble des types valeur / référence et des concepts d'objets immuables.
Pour autant que "==": Comme vous l'avez dit, "==" est une surcharge d'opérateur, et encore une fois, il a été implémenté pour une très bonne raison pour rendre le framework plus utile lorsque vous travaillez avec des chaînes.
la source
N'est pas aussi simple que les chaînes sont composées de tableaux de caractères. Je regarde les chaînes comme des tableaux de caractères []. Par conséquent, ils se trouvent sur le tas car l'emplacement de mémoire de référence est stocké sur la pile et pointe vers le début de l'emplacement de mémoire du tableau sur le tas. La taille de la chaîne n'est pas connue avant qu'elle ne soit allouée ... parfait pour le tas.
C'est pourquoi une chaîne est vraiment immuable car lorsque vous la changez même si elle est de la même taille, le compilateur ne le sait pas et doit allouer un nouveau tableau et affecter des caractères aux positions dans le tableau. Cela a du sens si vous considérez les chaînes comme un moyen pour les langages de vous éviter d'allouer de la mémoire à la volée (lire la programmation en C)
la source
Au risque d'obtenir un autre mystérieux vote négatif ... le fait que beaucoup mentionnent la pile et la mémoire en ce qui concerne les types de valeur et les types primitifs est parce qu'ils doivent s'insérer dans un registre du microprocesseur. Vous ne pouvez pas pousser ou faire sauter quelque chose vers / depuis la pile si cela prend plus de bits qu'un registre ... Les instructions sont, par exemple, "pop eax" - parce que eax a une largeur de 32 bits sur un système 32 bits.
Les types primitifs à virgule flottante sont gérés par la FPU, qui a une largeur de 80 bits.
Tout cela a été décidé bien avant qu'il n'y ait un langage OOP pour masquer la définition du type primitif et je suppose que le type de valeur est un terme qui a été créé spécifiquement pour les langages OOP.
la source