Ini juga bukan jawaban yang lengkap, tetapi saya punya beberapa ide.
Saya percaya saya telah menemukan penjelasan yang baik karena kita akan menemukan tanpa seseorang dari tim NET JIT yang menjawab.
MEMPERBARUI
Saya melihat sedikit lebih dalam, dan saya yakin saya telah menemukan sumber masalahnya. Tampaknya disebabkan oleh kombinasi bug dalam logika inisialisasi tipe JIT, dan perubahan dalam kompiler C # yang bergantung pada asumsi bahwa JIT berfungsi sebagaimana dimaksud. Saya pikir bug JIT ada di. NET 4.0, tetapi ditemukan oleh perubahan dalam kompiler untuk .NET 4.5.
Saya kira itu beforefieldinit
bukan satu-satunya masalah di sini. Saya pikir ini lebih sederhana dari itu.
Jenis System.String
mscorlib.dll dari .NET 4.0 berisi konstruktor statis:
.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
Dalam versi .NET 4.5 dari mscorlib.dll, String.cctor
(konstruktor statis) jelas tidak ada:
..... Tidak ada konstruktor statis :( .....
Dalam kedua versi String
jenisnya dihiasi dengan beforefieldinit
:
.class public auto ansi serializable sealed beforefieldinit System.String
Saya mencoba membuat jenis yang akan dikompilasi ke IL sama (sehingga memiliki bidang statis tetapi tidak ada konstruktor statis .cctor
), tetapi saya tidak bisa melakukannya. Semua tipe ini memiliki .cctor
metode dalam 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(); }
}
Dugaan saya adalah bahwa dua hal berubah antara .NET 4.0 dan 4.5:
Pertama: EE diubah sehingga secara otomatis akan diinisialisasi String.Empty
dari kode yang tidak dikelola. Perubahan ini mungkin dibuat untuk .NET 4.0.
Kedua: Kompiler berubah sehingga tidak memancarkan konstruktor statis untuk string, mengetahui bahwa String.Empty
akan ditugaskan dari sisi yang tidak dikelola. Perubahan ini tampaknya telah dibuat untuk .NET 4.5.
Tampaknya EE tidakString.Empty
segera menetapkan beberapa jalur optimasi. Perubahan yang dilakukan ke kompiler (atau apa pun yang diubah untuk String.cctor
menghilangkan) mengharapkan EE membuat penugasan ini sebelum kode pengguna dijalankan, tetapi tampaknya EE tidak membuat penugasan ini sebelum String.Empty
digunakan dalam metode tipe referensi kelas generik reified.
Terakhir, saya percaya bahwa bug tersebut mengindikasikan masalah yang lebih dalam pada logika inisialisasi tipe JIT. Tampaknya perubahan pada kompiler adalah kasus khusus untuk System.String
, tetapi saya ragu bahwa JIT telah membuat kasus khusus untuk ini System.String
.
Asli
Pertama-tama, WOW Orang-orang BCL menjadi sangat kreatif dengan beberapa optimasi kinerja. Banyak dari String
metode yang sekarang dilakukan dengan menggunakan Thread statis cache StringBuilder
objek.
Saya mengikuti petunjuk itu untuk sementara waktu, tetapi StringBuilder
tidak digunakan pada Trim
jalur kode, jadi saya memutuskan itu tidak bisa menjadi masalah Thread statis.
Saya pikir saya menemukan manifestasi aneh dari bug yang sama.
Kode ini gagal karena pelanggaran akses:
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);
}
}
Namun, jika Anda tanda komentar //new A<int>(out s);
di Main
kemudian kode bekerja dengan baik. Bahkan, jika A
diverifikasi dengan jenis referensi apa pun, program gagal, tetapi jika A
diverifikasi dengan jenis nilai apa pun maka kode tersebut tidak gagal. Juga jika Anda berkomentar keluar A
konstruktor statis, kode tidak pernah gagal. Setelah menggali ke dalam Trim
dan Format
, jelas bahwa masalahnya adalah yang Length
sedang digarisbawahi, dan bahwa dalam sampel di atas String
jenis belum diinisialisasi. Secara khusus, di dalam tubuh A
's konstruktor, string.Empty
tidak benar ditetapkan, meskipun di dalam tubuh Main
, string.Empty
yang ditugaskan dengan benar.
Sungguh menakjubkan bagi saya bahwa inisialisasi tipe String
entah bagaimana tergantung pada apakah atau tidak A
diverifikasi dengan tipe nilai. Satu-satunya teori saya adalah bahwa ada beberapa jalur kode JIT yang dioptimalkan untuk inisialisasi tipe generik yang dibagikan di antara semua jenis, dan bahwa jalur tersebut membuat asumsi tentang tipe referensi BCL ("tipe khusus?") Dan statusnya. Pandangan cepat meskipun kelas BCL lainnya dengan public static
bidang menunjukkan bahwa pada dasarnya semuanya menerapkan konstruktor statis (bahkan yang dengan konstruktor kosong dan tanpa data, seperti System.DBNull
dan System.Empty
. Tipe nilai BCL dengan public static
bidang tampaknya tidak menerapkan konstruktor statis ( System.IntPtr
, misalnya) Ini sepertinya mengindikasikan bahwa JIT membuat beberapa asumsi tentang inisialisasi tipe referensi BCL.
FYI Ini adalah kode JITed untuk dua versi:
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
}
Sisa kode ( Main
) identik antara dua versi.
EDIT
Selain itu, IL dari dua versi identik kecuali untuk panggilan A.ctor
masuk B.Main()
, di mana IL untuk versi pertama berisi:
newobj instance void class A`1<object>::.ctor(string&)
melawan
... A`1<int32>...
di yang kedua.
Satu hal yang perlu diperhatikan adalah bahwa kode JITed untuk A<int>.ctor(out string)
: sama dengan di versi non-generik.