56 votes

Chaîne vide en tant que cas spécial?

J'ai lu le quiz de Jon Skeet et je me suis demandé pourquoi le deuxième exemple ne fonctionne pas alors que le premier le fait.

Pourquoi cela donne true :

object x = new string("".ToArray());
object y = new string("".ToArray());
Console.WriteLine(x == y); //true

Mais celui-ci ne fonctionne pas :

var k="k";
//string.intern(k); // ne fonctionne pas
object x = new string(k.ToArray());
object y = new string(k.ToArray());
Console.WriteLine(x == y); //false

J'utilise fw 4.5 avec vs2010.

Heureusement j'ai aussi vs2005 installé, même résultats :

entrer la description de l'image ici

0 votes

Quelle version de .NET utilisez-vous qui vous permet de convertir un élément de chaîne unique en un tableau ? Ou peut-être devrais-je demander quels éléments vous utilisez ?

0 votes

@AdamZuckerman chaîne est un Ienumerable de caractères....

0 votes

Alors vous avez inclus LINQ aussi?

44voto

MarcinJuraszek Points 66084

Voici un article de blog d'Eric Lippert qui répond à votre question : String interning and String.Empty.

Il décrit une situation similaire :

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?

L'idée est que l'internage ne signifie pas que vous n'aurez qu'une seule instance d'une string particulière, même lorsqu'elle est internée. Seules les constantes au moment de la compilation sont internées par défaut. Cela signifie que le code suivant renvoie true :

var k1 = "k";
object k2 = "k";
Console.WriteLine(k1 == k2);

Mais, si vous essayez de créer une chaîne avec le contenu "k" de manière programmatique à l'exécution, par exemple en utilisant le constructeur string(char[]), en appelant ToString() sur un objet, en utilisant StringBuilder, etc., vous n'obtiendrez pas une chaîne internée par défaut. Ceci renvoie false :

var k1 = "k";
object k2 = new string("k".ToCharArray());
Console.WriteLine(k1 == k2);

Pourquoi ? Parce qu'interner des chaînes à l'exécution est coûteux.

Il n'y a pas de repas gratuit.

(...)

En bref, il n'est généralement pas utile d'interner toutes les chaînes.

Et concernant un comportement différent avec la chaîne vide :

Certaines versions du runtime .NET internent automatiquement la chaîne vide à l'exécution, d'autres non !

1 votes

Alors pourquoi est-ce que Console.WriteLine(str1 == str2); affiche true

2 votes

@Lui Parce qu'ils sont déclarés en tant que string et la classe String remplace l'opérateur == pour effectuer une comparaison de chaînes, et non une vérification d'égalité de référence.

1 votes

Quel rapport cela a-t-il avec la question ? Dans les exemples donnés, les chaînes de caractères sont toutes créées via les constructeurs. Aucune chaîne littérale n'est utilisée (et donc aucun interning).

11voto

BenM Points 210

Notez que la mise en commun des chaînes new dans le deuxième bloc de code les rend égales.

var k="k";
object x = string.Intern(new string(k.ToArray()));
object y = string.Intern(new string(k.ToArray()));
Console.WriteLine(x == y); //true

Il semble qu'il mette automatiquement en commun les chaînes vides, mais les chaînes non vides ne sont pas mises en commun sauf si c'est fait explicitement (ou ce sont des chaînes littérales qui sont toujours mises en commun).

Je suppose que oui, les chaînes vides sont traitées comme un cas spécial et sont automatiquement mises en commun, probablement parce que la vérification est si triviale qu'elle n'ajoute pas de réel impact sur les performances (nous pouvons affirmer en toute sécurité que N'IMPORTE QUELLE chaîne de longueur 0 est la chaîne vide et est identique à toute autre chaîne vide - toutes les autres chaînes nécessitent que nous examinions les caractères et pas seulement la longueur).

3 votes

Toutes les constantes chaînes de caractères sont interned par défaut.

2 votes

Cela retourne vrai car votre code est le même que object x = "k"; object y = "k" qui est interné par défaut. La question est Pourquoi le nouveau string(k.ToArray()) donne une nouvelle instance alors que new string("".ToArray()) donne une instance internée ?

6voto

Pavel Zhuravlev Points 1315

Le premier cas compare 2 références au même objet (String.Empty). L'appel de operator== pour 2 variables object provoque leur comparaison par référence et donne true.

Le deuxième cas produit 2 instances différentes de la classe de chaîne de caractères. Leur comparaison de référence donne false

Si vous donnez le type string à x et y dans le deuxième cas, l'override de string.operator== sera appelé et la comparaison donnera true

Notez que nous ne traitons pas directement l'internage des chaînes de caractères dans les deux cas. Les objets de chaîne que nous comparons sont créés en utilisant le constructeur string(char[]). Apparemment, ce constructeur est conçu pour renvoyer la valeur du champ string.Empty lorsqu'il est appelé avec un tableau vide comme argument.

La réponse publiée par MarcinJuraszek renvoie au blog de Lippert qui traite de l'internage des chaînes de caractères. Ce billet de blog discute d'un autre cas particulier de l'utilisation de la classe de chaîne de caractères. Considérez cet exemple du blog de Lippert mentionné ci-dessus :

object obj = "";
string str1 = "";
string str2 = String.Empty;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // parfois vrai, parfois faux ?!

Ce que nous voyons ici, c'est que l'assignation du littéral de chaîne vide ("") n'est pas garantie de produire la référence au champ statique en lecture seule System.String.Empty.

Regardons le langage intermédiaire (IL) pour l'expression object x = new string("".ToArray());:

IL_0001:  ldstr      ""
IL_0006:  call       !!0[] [System.Core]System.Linq.Enumerable::ToArray(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_000b:  newobj     instance void [mscorlib]System.String::.ctor(char[])
IL_0010:  stloc.0

L'internage peut (ou non) se produire à la ligne IL_0001. Que le littéral soit interné ou non, la méthode ToArray() produit un nouveau tableau vide et le String::.ctor(char[]) nous donne String.Empty.

Ce que nous voyons ici n'est pas le cas spécial de string.Empty mais plutôt l'un des effets secondaires du fait que la classe de string soit à la fois un type de référence et immuable. Il existe d'autres types immuables du framework qui ont des valeurs prédéfinies avec des sémantiques similaires (comme DateTime.MinValue). Mais autant que je sache, ces types du framework sont définis comme des struct contrairement au string qui est un type de référence. Les types de valeur sont une toute autre histoire... Il n'a pas de sens de retourner une instance de type prédéfinie fixe à partir d'un constructeur de classe mutable (le code appelant sera en mesure de changer cette instance et de provoquer un comportement imprévisible du type). Ainsi, les types de référence dont les constructeurs ne renvoient pas toujours de nouvelles instances peuvent exister à condition que ces types soient immuables. Je ne connais cependant pas d'autres types de ce genre dans le framework, sauf le string.

1 votes

Le deuxième cas produit 2 instances différentes de la classe string OK. si c'est le cas - pourquoi le premier ne produit pas 2 instances différentes de la classe string ?

1 votes

@RoyiNamir Probablement le constructeur de chaîne (char []) est conçu de cette manière. .NET a l'objet de chaîne vide spécial. Donc, il renvoie string.Empty lorsque le constructeur reçoit le tableau vide.

1 votes

@PavelZhuravlev: Y a-t-il d'autres types dont les constructeurs ne renvoient pas toujours de nouvelles instances ?

4voto

Matthew Points 10499

Mon hypothèse est pourquoi le premier renvoie vrai alors que le second renvoie faux :

Le premier résultat pourrait être une optimisation, prenez le code suivant

Enumerable.Empty() == Enumerable.Empty() // true

Donc, supposons que la méthode ToArray renvoie Enumerable.Empty() lorsque la chaîne est vide, cela expliquerait pourquoi le premier résultat renvoie vrai et le deuxième non, car il effectue une vérification de référence.

2voto

Todd Points 2386

According to http://msdn.microsoft.com/en-us/library/system.string.intern(v=vs.110).aspx

Dans le .NET Framework 3.5 Service Pack 1, la méthode Intern retrouve son comportement dans le .NET Framework 1.0 et 1.1 en ce qui concerne l'internement de la chaîne vide...

...Dans le. NET Framework 1.0, .NET Framework 1.1 et .NET Framework 3.5 SP1, ~les chaînes vides~ sont égales

Cela signifie que les chaînes vides sont internées par défaut, même lors de la construction à partir d'un tableau vide, et sont donc égales.

De plus:

La version 2.0 du .NET Framework introduit le membre d'énumération CompilationRelaxations.NoStringInterning

Cela vous fournit probablement un moyen de créer une manière cohérente de comparer, bien que comme le suggère @BenM, vous préféreriez utiliser explicitement la fonction Intern.

Étant donné l'encaissement qui se produit, vous pourriez aussi utiliser string.Equals au lieu de ==

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X