web-dev-qa-db-fra.com

En C #, pourquoi String est-il un type de référence qui se comporte comme un type de valeur?

Une chaîne est un type de référence même si elle présente 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 de faire référence au même objet.

Pourquoi la chaîne n'est-elle pas simplement un type de valeur alors?

340
Davy8

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 valeur sont (dans toutes les implémentations du CLR pour le moment) stockés dans la pile. L'allocation de chaînes par des chaînes casserait toutes sortes de choses: la pile ne représente que 1 Mo pour 32 bits et 4 Mo pour 64 bits, vous devez encadrer chaque chaîne, ce qui entraîne une pénalité de copie, vous ne pouvez pas les interner et l'utilisation de la mémoire serait ballon, etc ...

(Edit: ajout d'une clarification sur le stockage du type de valeur étant un détail d'implémentation, ce qui conduit à cette situation où nous avons un type avec une sémantique de valeur n'héritant pas de System.ValueType. Merci Ben.)

311
codekaizen

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 que sa valeur devait être copiée chaque fois qu'elle était transmise et renvoyée à une méthode, etc.

Il a une valeur sémantique pour garder le monde sain d’esprit. Pouvez-vous imaginer combien il serait difficile de coder si

string s = "hello";
string t = "hello";
bool b = (s == t);

définir b pour être false? Imaginez combien il serait difficile de coder n'importe quelle application.

54
jason

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 entraînent des frais généraux pour la construction, la destruction et le ramassage des ordures, car ils sont créés sur le tas. Les types de valeur, d’autre part, ont une surcharge lors des appels de méthode (si la taille des données est supérieure à celle d’un pointeur), car tout l’objet est copié plutôt qu’un pointeur. Comme les chaînes peuvent être (et sont généralement) beaucoup plus grandes que la taille d'un pointeur, elles sont conçues comme des types de référence. En outre, 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 valeur sont généralement immuables, car la sémantique des types de valeur 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 survient 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 de texte? Parce que c'est la sémantique la plus utile. Si deux chaînes sont égales en texte, elles peuvent ou non être la même référence à un objet en raison des optimisations. La comparaison de références est donc inutile, tandis que la comparaison de texte correspond presque toujours à ce que vous souhaitez.

Plus généralement, Strings a ce qu'on appelle sémantique de valeur. Il s'agit d'un concept plus général que celui des 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 savoir si l'implémentation sous-jacente est un type de référence ou un type de valeur, vous pouvez donc considérer ce détail comme une implémentation.

25
JacquesB

C’est une réponse tardive à une vieille question, mais toutes les autres réponses manquent, c’est-à-dire que .NET n’avait pas de génériques jusqu’à. 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 veiller à ce que les chaînes puissent être stockées de la manière la plus efficace dans les collections non génériques, tel que System.Collection.ArrayList.

Stocker un type de valeur dans une collection non générique nécessite une conversion spéciale en type object, appelé boxing. Lorsque le CLR encadre un type de valeur, il encapsule la valeur dans un System.Object et le stocke dans le segment de mémoire géré.

La lecture de la valeur de la collection nécessite l'opération inverse appelée unboxing.

Le boxing et le unboxing ont un coût non négligeable: la boxe nécessite une allocation supplémentaire, le unboxing nécessite une vérification de type.

Certaines réponses prétendent à tort que string n'aurait jamais pu être implémenté en tant que type de valeur car sa taille est variable. En réalité, il est facile d'implémenter une chaîne en tant que structure de données de longueur fixe à l'aide d'une stratégie d'optimisation de petite chaîne: les chaînes seraient stockées en mémoire directement sous forme d'une séquence de caractères Unicode, à l'exception des chaînes de grande taille qui seraient stockées sous forme de 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, la chaîne serait probablement une meilleure solution, avec une sémantique plus simple, une meilleure utilisation de la mémoire et une meilleure localisation du cache. Un List<string> contenant uniquement de petites chaînes aurait pu être un seul bloc de mémoire contigu.

13
ZunTzu

Non seulement les chaînes sont des types de référence immuables. Les délégués multi-cast aussi. C'est pourquoi il est prudent d'écrire

protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

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 ne sont-ils pas des types de valeur? Les auteurs précédents ont raison en ce qui concerne la taille de la pile, etc. J'ajouterais également que transformer des chaînes en types de référence permet de réduire la taille de l'Assemblée lorsque vous utilisez la même chaîne constante dans le programme. Si vous définissez

string s1 = "my string";
//some code here
string s2 = "my string";

Il est fort probable que les deux occurrences de la constante "ma chaîne" ne seront attribuées qu'une seule fois dans votre assembly.

Si vous souhaitez gérer les chaînes comme un type de référence habituel, placez la chaîne dans un nouveau StringBuilder (string s). Ou utilisez MemoryStreams.

Si vous souhaitez créer une bibliothèque, dans laquelle vous vous attendez à ce qu'une chaîne extrêmement importante soit passée dans vos fonctions, définissez un paramètre en tant que StringBuilder ou en tant que Stream.

8
Bogdan_Ch

En outre, la manière dont les chaînes sont mises en œuvre (différentes pour chaque plate-forme) et lorsque vous commencez à les assembler. Comme utiliser un StringBuilder. Il alloue une mémoire tampon dans laquelle vous pouvez copier, une fois que vous avez atteint la fin, il vous alloue encore plus de mémoire, dans l’espoir que, si vous effectuez une concaténation de grande taille, elle ne sera pas gênée.

Peut-être que Jon Skeet peut aider ici?

6
Chris

C'est principalement un problème de performance.

Le fait que les chaînes se comportent comme le type de valeur LIKE aide à l'écriture de code, mais le fait d'être un type de valeur aurait un impact considérable sur les performances.

Pour un regard en profondeur, jetez un œil à un article de Nice sur les chaînes dans le cadre .net.

5
Denis Troller

Comment pouvez-vous savoir que string est un type de référence? Je ne suis pas sûr que cela importe la façon dont cela est mis en œuvre. Les chaînes en C # sont immuables avec précision afin que vous n'ayez pas à vous soucier de ce problème.

2
please delete me

En réalité, les chaînes ont très peu de ressemblances avec les types valeur. Pour commencer, tous les types de valeur ne sont pas immuables, vous pouvez changer la valeur d'un Int32 à votre guise et si 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'agisse d'un type de référence, mais plutôt avec la gestion de la mémoire. Il est simplement plus efficace de créer un nouvel objet lorsque la taille de la chaîne change que de déplacer les éléments sur le tas géré. Je pense que vous mélangez des types valeur/référence et des concepts d’objets immuables.

En ce qui concerne "==": Comme vous l'avez dit, "==" est une surcharge d'opérateur, qui a également été mise en œuvre pour une très bonne raison de rendre le cadre plus utile lorsque vous travaillez avec des chaînes.

2
WebMatrix

En termes très simples, toute valeur ayant une taille définie peut être traitée comme un type de valeur.

2
saurav.net

N’est pas aussi simple que Strings est constitué de tableaux de caractères. Je regarde les chaînes comme des tableaux de caractères []. Par conséquent, ils sont sur le tas, car l'emplacement de mémoire de référence est stocké sur la pile et pointe au début de l'emplacement de mémoire de la matrice sur le tas. La taille de la chaîne n'est pas connue avant d'être allouée ... parfait pour le tas.

C’est pourquoi une chaîne est vraiment immuable, car lorsque vous la modifiez, même si elle a la même taille, le compilateur ne le sait pas et doit allouer un nouveau tableau et affecter des caractères aux positions du tableau. Il est logique de considérer les chaînes comme un moyen par lequel les langues vous évitent d'allouer de la mémoire à la volée (lisez C comme une programmation)

1
BionicCyborg