Ответ 1
Это также не полный ответ, но у меня есть несколько идей.
Я считаю, что я нашел хорошее объяснение, которое мы найдем, если кто-нибудь из команды .NET JIT не ответит.
UPDATE
Я посмотрел немного глубже, и я считаю, что нашел источник проблемы. По-видимому, это вызвано сочетанием ошибки в логике инициализации типа JIT и изменением компилятора С#, который основан на предположении, что 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
В версии 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
в методах ссылки тип reified generic classes.
Наконец, я считаю, что ошибка указывает на более глубокую проблему в логике инициализации типа JIT. Похоже, что изменение в компиляторе является особым случаем для System.String
, но я сомневаюсь, что JIT сделал специальный случай здесь для System.String
.
Оригинал
Прежде всего, WOW Люди BCL получили очень творческий подход с оптимизацией производительности. Многие из методов String
теперь выполняются с использованием статического кэшированного объекта StringBuilder
.
Я продолжил некоторое время, но StringBuilder
не используется в коде кода Trim
, поэтому я решил, что это не может быть проблемой статического потока.
Я думаю, что я обнаружил странное проявление одной и той же ошибки.
Этот код не работает с нарушением доступа:
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.
FYI Вот код 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
в B.Main()
, где IL для первой версии содержит:
newobj instance void class A`1<object>::.ctor(string&)
против
... A`1<int32>...
во втором.
Еще одна вещь, которую следует отметить, заключается в том, что код JITed для A<int>.ctor(out string)
: тот же, что и в не-генерической версии.