Это тоже не полный ответ, но у меня есть несколько идей.
Я полагаю, что нашел такое хорошее объяснение, какое мы найдем без ответа от кого-либо из команды .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.ctor
in B.Main()
, где IL для первой версии содержит:
newobj instance void class A`1<object>::.ctor(string&)
против
... A`1<int32>...
во-вторых.
Следует также отметить, что код JITed для A<int>.ctor(out string)
: такой же, как и в неуниверсальной версии.