Это тоже не полный ответ, но у меня есть несколько идей.
Я полагаю, что нашел такое хорошее объяснение, какое мы найдем без ответа от кого-либо из команды .NET JIT.
ОБНОВИТЬ
Я посмотрел немного глубже, и я думаю, что я нашел источник проблемы. По-видимому, это вызвано сочетанием ошибки в логике инициализации типа JIT и изменением в компиляторе C #, основанном на предположении, что JIT работает должным образом. Я думаю, что ошибка JIT существовала в .NET 4.0, но была обнаружена изменением компилятора для .NET 4.5.
Я не думаю, что beforefieldinitэто единственная проблема здесь. Я думаю, что это проще, чем это.
Тип System.Stringв mscorlib.dll из .NET 4.0 содержит статический конструктор:
.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
В .NET 4.5 версии mscorlib.dll 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оно используется в методах базовых классов reified ссылочного типа.
Наконец, я считаю, что ошибка указывает на более глубокую проблему в логике инициализации типа JIT. Похоже, что изменение в компиляторе является особым случаем System.String, но я сомневаюсь, что JIT сделал особый случай здесь System.String.
оригинал
Прежде всего, WOW Люди BCL стали очень креативными с некоторыми оптимизациями производительности. Многие из Stringметодов теперь выполняются с использованием статического кешируемого StringBuilderобъекта Thread .
Некоторое время я следовал этому примеру, но 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 («специальные типы?») И их состоянии. Краткий обзор других классов BCL с public staticполями показывает, что в основном все они реализуют статический конструктор (даже классы с пустыми конструкторами и без данных, например, System.DBNullи System.Empty. Типы значений BCL с public staticполями, по-видимому, не реализуют статический конструктор ( System.IntPtrнапример) Кажется, это указывает на то, что JIT делает некоторые предположения об инициализации ссылочного типа BCL.
К вашему сведению, код JIT для двух версий:
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.ctorin B.Main(), где IL для первой версии содержит:
newobj instance void class A`1<object>::.ctor(string&)
против
... A`1<int32>...
во-вторых.
Следует также отметить, что код JITed для A<int>.ctor(out string): такой же, как и в неуниверсальной версии.