42 votes

Changement de comportement de string.Empty (ou System.String :: Empty) dans .NET 4.5

Version courte:

Le code C#

typeof(string).GetField("Empty").SetValue(null, "Hello world!");
Console.WriteLine(string.Empty);

lors de la compilation et de l'exécution, donne de sortie "Hello world!" sous .NET version 4.0 et versions antérieures, mais qui donne des "" sous .NET 4.5 et .NET 4.5.1.

Comment écrire à un champ soit ignoré comme ça, ou qui réinitialise ce domaine?

Version longue:

Je n'ai jamais vraiment compris pourquoi l' string.Empty champ (également connu en tant que [mscorlib]System.String::Empty) n'est pas const (aka. literal), voir "Pourquoi n'est-il pas de Chaîne.Vide une constante?". Cela signifie que, par exemple, en C#, nous ne pouvons pas utiliser string.Empty dans les situations suivantes:

  • En switch déclaration dans la forme case string.Empty:
  • Comme la valeur par défaut d'un paramètre facultatif, comme void M(string x = string.Empty) { }
  • Lors de l'application d'un attribut, comme [SomeAttribute(string.Empty)]
  • D'autres situations où une constante de compilation est nécessaire

qui a des implications pour le bien-connu "guerre de religion" sur la question de l'utilisation string.Empty ou "", voir "En C#, dois-je utiliser des chaînes de caractères.Vide ou Chaîne.Vide ou "de"?".

Il y A quelques années je m'amusais à en définissant Empty d'une autre chaîne instance par la réflexion, et de voir comment de nombreuses parties de la BCL a commencé à se comporter bizarrement à cause de cela. C'était assez nombreux. Et le changement de l' Empty référence semble persister pendant la durée complète de l'application. Maintenant, l'autre jour, j'ai essayé de répéter que peu de stunt, mais ensuite, à l'aide d'un .NET 4.5 machine, et je ne pouvais pas faire plus.

(NB! Si vous en avez .NET 4.5 sur votre machine, probablement votre PowerShell encore utilise une ancienne version de .NET, alors essayez de copier-coller [String].GetField("Empty").SetValue($null, "Hello world!") en PowerShell pour voir quelques-uns des effets de la modification de cette référence.)

Quand j'ai essayé de chercher une raison à cela, je suis tombé sur le fil intéressant "Quelle est la cause de cette FatalExecutionEngineError dans .NET 4.5 beta?". Dans la accepté de répondre à cette question, est-il noté que jusqu'à la version 4.0, System.String avaient un constructeur statique .cctor dans lequel le champ Empty a été fixé (dans le source C#, qui serait probablement juste être un champ d'initialiseur, bien sûr), tout en 4,5 aucun constructeur statique existe. Dans les deux versions, le champ lui-même semble le même:

.field public static initonly string Empty

(comme on le voit avec l'IL DASM).

Pas d'autres domaines que la String::Empty semble être affecté. Comme un exemple, j'ai testé avec System.Diagnostics.Debugger::DefaultCategory. Ce cas semble analogue: Une classe scellée contenant un static readonly (static initonly) champ de type string. Mais dans ce cas, il fonctionne très bien pour modifier la valeur (de référence) par le biais de la réflexion.

Retour à la question:

Comment est-il possible, techniquement, c' Empty ne semble pas changer (4,5) quand j'ai mis le champ? J'ai vérifié que le compilateur C# ne pas "tricher" avec la lecture, c'sorties IL aime:

ldsfld     string [mscorlib]System.String::Empty

si le champ doit être lu.


Edit après bounty a été mis sur ma question: Notez que l'opération d'écriture (qui a besoin de la réflexion, pour sûr, car le champ est - readonly (un.k.un. initonly dans l'IL)) en fait, il fonctionne comme prévu. C'est la lecture d'une utilisation anormale. Si vous lisez avec la réflexion, en typeof(string).GetField("Empty").GetValue(null),, tout est normal (c'est à dire le changement de valeur est vu). Voir les commentaires ci-dessous.

Donc la question est: Pourquoi cette nouvelle version du cadre de tricher lorsqu'il lit ce domaine particulier?

22voto

280Z28 Points 49515

La différence réside dans le JIT pour la nouvelle version de .NET, qui, apparemment, optimise les références à String.Empty par l'in-lining une référence à un particulier String exemple, plutôt que de charger la valeur stockée dans l' Empty champ. Ceci est justifié en vertu de la définition de l' init seule contrainte ECMA-335 Partition I §8.6.1.2, qui peut être interprété comme la valeur de l' String.Empty champ ne changera pas après l' String classe est initialisée.

3voto

Veovis Points 31

Je n'ai pas de réponse, juste un indice, peut-être.

La seule différence que je vois entre String::Empty et System.Diagnostics.Debugger::DefaultCategory est la première est taggés avec __DynamicallyInvokableAttribute.

Je n'ai pas connu la signification de ce sans-papiers de l'attribut. Une question au sujet de cet attribut a été demandé sur DONC: qu'est-Ce que l' __DynamicallyInvokable attribut?

Je ne peux que supposer que cet attribut est prise par le runtime de faire quelques mise en cache ?

2voto

Ben Voigt Points 151460

Parce qu'il peut.

La valeur de ces définies par le système initonly champs sont invariants globaux pour la .NET runtime. Si ces invariants sont cassés, il n'y a plus aucune garantie concernant le comportement.

En C++, nous aurions probablement une règle désignant ce que causer un comportement indéfini. Dans .NET, il est aussi un comportement indéfini, tout simplement par l'absence de toute règle en disant ce qui se passe lors de l' System.String.Empty.Length > 0. L'ensemble de la spécification de toutes les couches de .NET et C# de décrire le comportement lors de l' System.String.Empty.Length == 0 et tout un tas d'invariants aussi tenir.

Pour plus d'informations sur les optimisations qui varient entre les temps d'exécution et les implications, voir les réponses à

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