Добавьте трассировку:
class Program
{
static void Main(string[] args)
{
StringHolder.ImportantString = "Howdy";
var x = Singleton.Current;
Console.WriteLine("Done");
}
}
public class Singleton
{
public static Singleton Current { get; } = new Singleton();
private Singleton()
{
try
{
Console.WriteLine("Calling ctor.");
var x = StringHolder.ImportantString.ToLower(); // Null Reference occurs here when Optimize Code is on.
Console.WriteLine("ctor called.");
}
catch(Exception e)
{
Console.WriteLine($"ctor failed with {e.GetType()}");
}
}
}
public static class StringHolder
{
private static string importantString;
public static string ImportantString
{
get
{
Console.WriteLine("Getting ImportantString");
return importantString;
}
set
{
Console.WriteLine("Setting ImportantString");
importantString = value;
Console.WriteLine("ImportantString set");
}
}
}
и запустите программу в режиме Debug
.
Вывод:
Setting ImportantString
ImportantString set
Calling ctor.
Getting ImportantString
ctor called.
Done
Запустите программу в режиме Release
.
Вывод:
Calling ctor.
Getting ImportantString
ctor failed with System.NullReferenceException
Setting ImportantString
ImportantString set
Done
Debug
mode IL
из Main
is:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 30 (0x1e)
.maxstack 1
.locals init ([0] class OptimizationIssue.Singleton x)
IL_0000: nop
IL_0001: ldstr "Howdy"
IL_0006: call void OptimizationIssue.StringHolder::set_ImportantString(string)
IL_000b: nop
IL_000c: call class OptimizationIssue.Singleton OptimizationIssue.Singleton::get_Current()
IL_0011: stloc.0
IL_0012: ldstr "Done"
IL_0017: call void [mscorlib]System.Console::WriteLine(string)
IL_001c: nop
IL_001d: ret
} // end of method Program::Main
Release
mode IL
of Main
is:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 27 (0x1b)
.maxstack 8
IL_0000: ldstr "Howdy"
IL_0005: call void OptimizationIssue.StringHolder::set_ImportantString(string)
IL_000a: call class OptimizationIssue.Singleton OptimizationIssue.Singleton::get_Current()
IL_000f: pop
IL_0010: ldstr "Done"
IL_0015: call void [mscorlib]System.Console::WriteLine(string)
IL_001a: ret
} // end of method Program::Main
IL
s для обоих выглядят одинаково.
Debug
mode disassembly
из Main
is:
{
01A3084A in al,dx
01A3084B push edi
01A3084C push esi
01A3084D push ebx
01A3084E sub esp,38h
01A30851 mov esi,ecx
01A30853 lea edi,[ebp-44h]
01A30856 mov ecx,0Eh
01A3085B xor eax,eax
01A3085D rep stos dword ptr es:[edi]
01A3085F mov ecx,esi
01A30861 mov dword ptr [ebp-3Ch],ecx
01A30864 cmp dword ptr ds:[16042E8h],0
01A3086B je 01A30872
01A3086D call 7247F5A0
01A30872 xor edx,edx
01A30874 mov dword ptr [ebp-40h],edx
01A30877 nop
StringHolder.ImportantString = "Howdy";
01A30878 mov ecx,dword ptr ds:[4402334h]
01A3087E call 01A30458
01A30883 nop
var x = Singleton.Current;
01A30884 call 01A30468
01A30889 mov dword ptr [ebp-44h],eax
01A3088C mov eax,dword ptr [ebp-44h]
01A3088F mov dword ptr [ebp-40h],eax
Console.WriteLine("Done");
01A30892 mov ecx,dword ptr ds:[4402338h]
01A30898 call 70DD3CD4
01A3089D nop
}
01A3089E nop
01A3089F lea esp,[ebp-0Ch]
01A308A2 pop ebx
01A308A3 pop esi
01A308A4 pop edi
01A308A5 pop ebp
01A308A6 ret
Release
mode disassembly
из Main
is:
StringHolder.ImportantString = "Howdy";
00D51072 in al,dx
00D51073 mov ecx,dword ptr ds:[3A32344h]
00D51079 call dword ptr ds:[0BF4DF4h]
Console.WriteLine("Done");
00D5107F mov ecx,dword ptr ds:[3A32348h]
00D51085 call 70DD3CD4
00D5108A pop ebp
00D5108B ret
disassembly
с сильно отличаются. JIT-compilation
имеет значение. Похоже JIT-compilation
удаляет неиспользованную переменную. Но он по-прежнему создает тип OptimizationIssue.Singleton
, , вызывающий его статический конструктор перед выполнением метода Main
. Статический конструктор создается неявно из-за public static Singleton Current { get; } = new Singleton();
в коде. Когда он называется StringHolder.ImportantString
еще не установлен, он равен нулю, поэтому NullReferenceException
выбрасывается при попытке вызвать ToLower()
для него.
Удалите var x = Singleton.Current;
из Main
и посмотрите disassembly
:
StringHolder.ImportantString = "Howdy";
00FE084A in al,dx
00FE084B mov ecx,dword ptr ds:[3BF2334h]
00FE0851 call dword ptr ds:[0CC4DF4h]
Console.WriteLine("Done");
00FE0857 mov ecx,dword ptr ds:[3BF2338h]
00FE085D call 70DD3CD4
00FE0862 pop ebp
00FE0863 ret
Это мало что меняет. Мы удалили вручную то, что компилятор удалил автоматически. Но тип Singleton
больше не упоминается, поэтому статический конструктор не вызывается, поэтому нет исключения.
Addig static Singleton() { }
изменяется disassembly
на:
StringHolder.ImportantString = "Howdy";
0169084A in al,dx
0169084B mov ecx,dword ptr ds:[41F2334h]
01690851 call dword ptr ds:[1334DF4h]
var x = Singleton.Current;
01690857 call dword ptr ds:[1334E60h]
Console.WriteLine("Done");
0169085D mov ecx,dword ptr ds:[41F2338h]
01690863 call 70DD3CD4
01690868 pop ebp
01690869 ret
Теперь он по какой-то причине не удаляет var x = Singleton.Current;
и вызывает Singleton
статический конструктор просто перед выполнением строки после того, как StringHolder.ImportantString
было установлено, поэтому исключений нет.
Это оптимизировано JIT-compilation
магия. Не надейся на это. Удалите static Singleton() { }
из Singleton
и лучше добавьте [MethodImpl(MethodImplOptions.NoOptimization)]
в Main
. (Или намного лучше не создавайте объекты, которые вы никогда не используете.)
Тогда вывод:
Setting ImportantString
ImportantString set
Calling ctor.
Getting ImportantString
ctor called.
Done
disassembly
- это:
StringHolder.ImportantString = "Howdy";
00DA084A in al,dx
00DA084B mov ecx,dword ptr ds:[3A42334h]
00DA0851 call dword ptr ds:[0B14DF4h]
var x = Singleton.Current;
00DA0857 call dword ptr ds:[0B14E60h]
Console.WriteLine("Done");
00DA085D mov ecx,dword ptr ds:[3A42338h]
00DA0863 call 70DD3CD4
00DA0868 pop ebp
00DA0869 ret
И все отлично работает.
Мораль истории : JIT-compilation
с оптимизациями полон встраивания, удаления и многих других вещей, которые трудно предвидеть и которые могут странным образом изменить поведение вашего кода .