Ce n'est pas une réponse, mais j'ai quelques idées.
Je crois que j'ai trouvé une bonne explication que nous trouverons sans quelqu'un de la .NET JIT équipe de réponse.
Mise à JOUR
J'ai regardé un peu plus loin, et je crois avoir trouvé la source du problème. Il semble être causée par une combinaison d'un bug dans l'équipe type de l'initialisation de la logique, et un changement dans le compilateur C# qui repose sur l'hypothèse que l'équipe fonctionne comme prévu. Je pense que le JIT bug existait dans .NET 4.0, mais il a été découvert par le changement dans le compilateur .NET 4.5.
Je ne pense pas qu' beforefieldinit
est la seule question ici. Je pense que c'est plus simple.
Le type System.String
dans mscorlib.dll partir de .NET 4.0 contient un constructeur statique:
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr ""
IL_0005: stsfld string System.String::Empty
IL_000a: ret
} // end of method String::.cctor
Dans l' .NET version 4.5 de mscorlib.dll, String.cctor
(le constructeur statique) est aux abonnés absents:
..... Aucun constructeur statique :( .....
Dans les deux versions de l' String
type est orné beforefieldinit
:
.class public auto ansi serializable sealed beforefieldinit System.String
J'ai essayé de créer un type de dresser le IL de même (alors qu'il a les champs statiques, mais aucun constructeur statique .cctor
), mais je ne pouvais pas le faire. Tous ces types ont un .cctor
méthode de l'IL:
public class MyString1 {
public static MyString1 Empty = new MyString1();
}
public class MyString2 {
public static MyString2 Empty = new MyString2();
static MyString2() {}
}
public class MyString3 {
public static MyString3 Empty;
static MyString3() { Empty = new MyString3(); }
}
Ma conjecture est que les deux choses ont changé entre les deux .NET 4.0 et 4.5:
D'abord: L'EE a été modifié de sorte qu'il serait d'initialiser automatiquement String.Empty
de code non managé. Ce changement a été probablement faite pour .NET 4.0.
Deuxième: Le compilateur changé, de sorte qu'il n'émettent pas un constructeur statique pour la chaîne, sachant qu' String.Empty
seraient affectés de la non géré side. Ce changement semble avoir été faite pour .NET 4.5.
Il semble que l'EE n'a pas attribuer String.Empty
assez vite le long de certaines d'optimisation des chemins. La modification apportée à l'un compilateur (ou quel que soit modifiée pour rendre String.cctor
disparaissent) devrait EE de procéder à cette affectation avant tout code d'utilisateur exécute, mais il semble que l'EE ne permet pas de faire ce travail avant d' String.Empty
est utilisé dans les méthodes de type de référence réifiée des classes génériques.
Enfin, je pense que le bug est le signe d'un problème plus profond dans l'équipe type de l'initialisation de la logique. Il semble que le changement dans le compilateur est un cas particulier pour System.String
, mais je doute que l'équipe a fait un cas particulier ici pour System.String
.
D'origine
Tout d'abord, WOW La BCL gens ont obtenu de très créatif avec quelques optimisations de performances. Beaucoup de de la String
méthodes sont désormais effectuées à l'aide d'un Fil statique de cache StringBuilder
objet.
Je l'ai suivi dans cette voie pendant un certain temps, mais StringBuilder
n'est pas utilisé sur la Trim
chemin de code, j'ai donc décidé qu'il ne pouvait pas être un Thread problème statique.
Je crois que j'ai trouvé une étrange manifestation de la même bug.
Ce code ne fonctionne pas avec une violation d'accès:
class A<T>
{
static A() { }
public A(out string s) {
s = string.Empty;
}
}
class B
{
static void Main() {
string s;
new A<object>(out s);
//new A<int>(out s);
System.Console.WriteLine(s.Length);
}
}
Toutefois, si vous supprimez //new A<int>(out s);
en Main
alors que le code fonctionne très bien. En fait, si l' A
est réifiée avec n'importe quel type de référence, le programme ne parvient pas, mais si A
est réifiée avec n'importe quel type de valeur que le code n'échoue pas. Aussi, si vous commentez A
s'constructeur statique, le code n'échoue jamais. En creusant, en Trim
et Format
, il est clair que le problème est qu' Length
est inline, et que dans ces échantillons au-dessus de l' String
type n'a pas été initialisé. En particulier, à l'intérieur du corps de l' A
s'constructeur, string.Empty
n'est pas correctement affecté, bien qu'à l'intérieur du corps de l' Main
, string.Empty
est attribuée correctement.
Il est étonnant pour moi que le type d'initialisation de String
d'une certaine manière dépend de si oui ou non A
est réifiée avec un type de valeur. Ma théorie est qu'il y a de l'optimisation JIT chemin de code pour le type générique-initialisation qui est partagé entre tous les types, et que ce chemin fait des hypothèses sur la BCL types de référence ("types particuliers?") et de leur état. Un rapide coup d'oeil si d'autres BCL classes avec public static
champs montre que, fondamentalement, tous d'entre eux de mettre en œuvre un constructeur statique (même ceux avec les constructeurs et les données, comme System.DBNull
et System.Empty
. BCL types de valeur avec public static
champs ne semblent pas mettre en œuvre un constructeur statique (System.IntPtr
, par exemple). Cela semble indiquer que le JIT fait quelques hypothèses sur la BCL type de référence de l'initialisation.
Pour info Voici le JITed code pour les deux versions:
A<object>.ctor(out string)
:
public A(out string s) {
00000000 push rbx
00000001 sub rsp,20h
00000005 mov rbx,rdx
00000008 lea rdx,[FFEE38D0h]
0000000f mov rcx,qword ptr [rcx]
00000012 call 000000005F7AB4A0
s = string.Empty;
00000017 mov rdx,qword ptr [FFEE38D0h]
0000001e mov rcx,rbx
00000021 call 000000005F661180
00000026 nop
00000027 add rsp,20h
0000002b pop rbx
0000002c ret
}
A<int32>.ctor(out string)
:
public A(out string s) {
00000000 sub rsp,28h
00000004 mov rax,rdx
s = string.Empty;
00000007 mov rdx,12353250h
00000011 mov rdx,qword ptr [rdx]
00000014 mov rcx,rax
00000017 call 000000005F691160
0000001c nop
0000001d add rsp,28h
00000021 ret
}
Le reste du code (Main
) est identique entre les deux versions.
MODIFIER
En outre, le IL de les deux versions est identique sauf pour l'appel à la A.ctor
en B.Main()
, où la IL pour la première version contient:
newobj instance void class A`1<object>::.ctor(string&)
rapport
... A`1<int32>...
dans la seconde.
Une autre chose à noter est que le JITed code pour A<int>.ctor(out string)
: c'est le même que dans le non-générique.