在我的应用程序中,我有一个静态对象,其中包含另一个对象的实例。我有一个单例,在其构造函数中称为实例持有者。实例所有者的实例是在调用Singleton之前设置的,因此可以正常工作。当我打开优化代码时,就会出现问题。突然,我得到对实例所有者的实例的空引用。
在此示例中,我将Visual Studio 2017与.NET 4.6.1中的控制台应用程序一起使用。我的主要应用是WPF应用,我正在App()构造函数中调用Singleton。
class Program
{
static void Main(string[] args)
{
StringHolder.ImportantString = "Howdy";
var x = Singleton.Current;
}
}
public static class StringHolder
{
public static string ImportantString { get; set; }
}
public class Singleton
{
public static Singleton Current { get; } = new Singleton();
private Singleton()
{
var x = StringHolder.ImportantString.ToLower(); // Null Reference occurs here when Optimize Code is on.
}
}
这似乎是由于在Main的第一行被调用之前在 中创建了Singleton引起的。添加Console.WriteLine()调用即可显示这种情况。
一种解决方案是将Main函数分成多个部分:设置ImportantString并调用“ Part2”函数,该函数调用Singleton。
static void Main(string[] args)
{
StringHolder.ImportantString = "Howdy";
Part2();
}
public static void Part2()
{
var x = Singleton.Current;
}
但是,此解决方案不适用于此处的示例代码:它仅适用于我的主项目。我不太确定为什么。
另一种解决方案是更改Singleton.Current的工作方式:
public static Singleton _current;
public static Singleton Current => _current ?? (_current = new Singleton());
(这显然可以解决此问题,因为静态属性直到被调用时才会实例化。)
第三个解决方案是添加一个静态构造函数:
static Singleton() { }
但是,修复我的代码不是我关心的事情。我的担心如下:
答案 0 :(得分:1)
添加一些跟踪:
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
{{1}的Debug
模式IL
为:
Main
{{1}的.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
为:
IL
两者的 Main
看起来都差不多。
{{1}的.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
为:
Debug
{{1}的disassembly
模式Main
为:
{
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
的差异很大。 disassembly
有所作为。似乎Main
删除了未使用的变量。但是它仍然会创建类型 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
时,它为null,因此尝试在其上调用OptimizationIssue.Singleton
时会抛出Main
。
从public static Singleton Current { get; } = new Singleton();
移除StringHolder.ImportantString
并查看NullReferenceException
:
ToLower()
它变化不大。我们手动删除了编译器自动删除的内容。但是不再提到var x = Singleton.Current;
类型,因此不会调用静态构造函数,因此也不例外。
Addig 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
,而是在设置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)]
一切正常。
故事的寓意:具有{strong>优化的Main
充满了内联,删除和许多其他难以预料的东西,并且能够改变您的代码行为以一种奇怪的方式。