यह भी एक पूर्ण उत्तर नहीं है, लेकिन मेरे पास कुछ विचार हैं।
मेरा मानना है कि मैंने .NET जेआईटी टीम से किसी के बिना उत्तर के रूप में अच्छा स्पष्टीकरण पाया है।
अपडेट करें
मैं थोड़ा गहरा लग रहा था, और मुझे विश्वास है कि मुझे इस मुद्दे का स्रोत मिल गया है। यह 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 के बीच दो चीजें बदल गई हैं:
पहला: ईई को बदल दिया गया था ताकि यह स्वचालित रूप से प्रारंभ हो जाए String.Empty
से अनवांटेड कोड से हो जाए। यह परिवर्तन संभवतः .NET 4.0 के लिए किया गया था।
दूसरा: संकलक बदल गया ताकि यह स्ट्रिंग के लिए एक स्थिर निर्माता का उत्सर्जन न करे, यह जानकर String.Empty
इसे अप्रबंधित पक्ष से सौंपा जाएगा। यह परिवर्तन .NET 4.5 के लिए किया गया प्रतीत होता है।
ऐसा प्रतीत होता है कि ईई जल्द ही कुछ अनुकूलन पथों के साथ पर्याप्त रूप से असाइन नहीं होता है String.Empty
। संकलक के लिए किए गए परिवर्तन (या जो कुछ भी String.cctor
गायब करने के लिए बदल गया है) को उम्मीद थी कि ईई इस असाइनमेंट को किसी भी उपयोगकर्ता कोड को निष्पादित करने से पहले कर देगा, लेकिन ऐसा प्रतीत होता है कि ईई इस असाइनमेंट String.Empty
को संदर्भ प्रकार के पुनरीक्षित सामान्य वर्गों के तरीकों में उपयोग करने से पहले नहीं करता है ।
अंत में, मेरा मानना है कि बग JIT टाइप-इनिशियलाइज़ेशन लॉजिक में एक गहरी समस्या का संकेत है। ऐसा प्रतीत होता है कि कंपाइलर में बदलाव एक विशेष मामला है System.String
, लेकिन मुझे संदेह है कि जेआईटी ने यहां एक विशेष मामला बनाया है System.String
।
मूल
सबसे पहले, वाह 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
मूल्य मान के साथ पुन: परिभाषित किया जाता है या नहीं । मेरा एकमात्र सिद्धांत यह है कि जेनेरिक टाइप-इनिशियलाइज़ेशन के लिए कुछ अनुकूलन जेआईटी कोड पथ है जो सभी प्रकारों के बीच साझा किया जाता है, और वह रास्ता बीसीएल संदर्भ प्रकारों ("विशेष प्रकार?") और उनके राज्य के बारे में धारणा बनाता है। public static
खेतों के साथ अन्य बीसीएल वर्गों का एक त्वरित रूप से पता चलता है कि मूल रूप से उनमें से सभी एक स्थिर निर्माता को लागू करते हैं (यहां तक कि खाली निर्माणकर्ताओं और कोई डेटा नहीं है, जैसे System.DBNull
और फ़ील्ड के System.Empty
साथ बीसीएल मूल्य प्रकार public static
एक स्थिर निर्माता को लागू करने के लिए नहीं लगते हैं ()System.IntPtr
उदाहरण के लिए) । यह इंगित करता है कि जेसीएल बीसीएल संदर्भ प्रकार आरंभीकरण के बारे में कुछ धारणाएं बनाता है।
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)
: गैर-जेनेरिक संस्करण के समान है।