这也不是一个完整的答案,但是我有一些想法。
我相信,如果没有.NET JIT团队的任何人回答,我会找到很好的解释。
更新
我看上去更加深入,我相信已经找到了问题的根源。这似乎是由于JIT类型初始化逻辑中的错误以及C#编译器中的更改所引起的,这些更改依赖于JIT按预期工作的假设。我认为JIT错误存在于.NET 4.0中,但未被.NET 4.5编译器的更改所揭示。
我认为这beforefieldinit
不是唯一的问题。我认为这比这简单。
System.String
.NET 4.0中mscorlib.dll中的类型包含一个静态构造函数:
.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
在mscorlib.dll的.NET 4.5版本中,String.cctor
(静态构造函数)显然不存在:
.....没有静态构造函数:( .....
在这两个版本中,String
类型均带有beforefieldinit
:
.class public auto ansi serializable sealed beforefieldinit System.String
我试图创建一个将类似地编译为IL的类型(以便它具有静态字段,但没有静态构造函数.cctor
),但是我做不到。所有这些类型.cctor
在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(); }
}
我的猜测是,.NET 4.0和4.5之间发生了两件事:
第一:更改了EE,以便它将自动String.Empty
从非托管代码初始化。此更改可能是针对.NET 4.0进行的。
其次:编译器进行了更改,以使其不会为字符串发出静态构造函数,因为它知道该字符串String.Empty
是从非托管方分配的。.NET 4.5似乎已进行了此更改。
看来EE 在String.Empty
某些优化路径上分配得不够快。对编译器所做的更改(或所做的任何更改都会String.cctor
消失)希望EE在执行任何用户代码之前先进行此分配,但是看来EE String.Empty
在引用类型化通用类的方法中使用之前并未进行此分配。
最后,我认为该错误表明JIT类型初始化逻辑中存在更深层的问题。看来编译器中的更改是特殊情况System.String
,但我怀疑JIT在这里已经做了特殊情况System.String
。
原版的
首先,WOW BCL人员通过一些性能优化变得非常有创造力。 很多的String
方法,现在使用线程静态缓存的执行StringBuilder
对象。
我跟踪了一段时间,但是StringBuilder
没有在Trim
代码路径上使用它,所以我决定它不可能是Thread静态问题。
我想我发现了同一错误的奇怪表现。
此代码因访问冲突而失败:
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);
}
}
但是,如果您不加注释//new A<int>(out s);
,Main
则代码可以正常工作。实际上,如果A
使用任何引用类型进行了形式化,则程序将失败,但是如果A
使用任何值类型进行了形式化,则代码不会失败。同样,如果您注释掉A
的静态构造函数,则代码永远不会失败。深入研究Trim
和之后Format
,很明显问题是Length
正在内联,并且在以上这些示例中,该String
类型尚未初始化。特别地,主体内A
的构造,string.Empty
不正确地分配,虽然在身体内部Main
,string.Empty
被正确分配。
令我惊讶的是,String
某种程度上的类型初始化取决于是否A
使用值类型进行了形式化。我唯一的理论是,存在针对所有类型共享的一些优化的用于通用类型初始化的JIT代码路径,并且该路径对BCL引用类型(“特殊类型?”)及其状态进行了假设。快速浏览一下其他带有public static
字段的BCL类,表明它们基本上都实现了静态构造函数(即使是那些带有空构造函数且没有数据的类,例如System.DBNull
和System.Empty
。带有public static
字段的BCL值类型似乎也没有实现静态构造函数(System.IntPtr
例如)这似乎表明JIT对BCL引用类型初始化进行了一些假设。
仅供参考,这是两个版本的JITed代码:
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
}
Main
两个版本之间的其余代码()相同。
编辑
此外,两个版本的IL相同,不同之处在于对A.ctor
in 的调用B.Main()
,其中第一个版本的IL包含:
newobj instance void class A`1<object>::.ctor(string&)
与
... A`1<int32>...
在第二。
要注意的另一件事是:的JITed代码A<int>.ctor(out string)
与非通用版本中的代码相同。