请考虑以下代码:
enum Test {
OptionOne,
OptionTwo
}
List<string> testValues = new List<string> { ... } // A huge collection of strings
foreach(var val in testValues)
{
if(val == Test.OptionOne.ToString()) // **Here**
{
// Do something
}
}
Run Code Online (Sandbox Code Playgroud)
编译器是否会优化调用,Test.OptionOne.ToString()还是会为testValues集合中的每个项调用它?
您的问题是循环不变量分析之一 - 编译器是否可以在循环中检测某些表达式,该循环不依赖于其评估的循环状态,并且没有副作用?
有充分的理由希望编译器可以满足这两个命题 - 编译器可以足够聪明地知道调用ToString()枚举不会发生变化; 并且调用ToString()枚举没有任何明显的副作用.
可能有理由说明编译器会主动决定不提升不变量 - 也许调用函数比在堆栈上存储额外变量更快.
问题归结为它是否确实存在.
我使用针对.Net 4.6的VS2012编译了以下程序,并在启用优化的情况下编译.似乎编译器没有选择将不变量提升出循环:
public static void Main()
{
for( int i = 0; i < 10; i++ )
{
Console.Out.WriteLine( i );
Console.Out.WriteLine( Test.Option1.ToString() );
}
}
public enum Test
{
Option1,
Option2,
Option3
}
Run Code Online (Sandbox Code Playgroud)
这是我使用ILSpy 2.3.1获得的程序中的原始IL.请注意ToString(),在循环中间的调用.
.method public hidebysig static
void Main () cil managed
{
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2050
// Code size 46 (0x2e)
.maxstack 2
.entrypoint
.locals init (
[0] int32 i
)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_0028
// loop start (head: IL_0028)
IL_0004: call class [mscorlib]System.IO.TextWriter [mscorlib]System.Console::get_Out()
IL_0009: ldloc.0
IL_000a: callvirt instance void [mscorlib]System.IO.TextWriter::WriteLine(int32)
IL_000f: call class [mscorlib]System.IO.TextWriter [mscorlib]System.Console::get_Out()
IL_0014: ldc.i4.0
IL_0015: box TestProject.Program/Test
---> IL_001a: callvirt instance string [mscorlib]System.Object::ToString()
IL_001f: callvirt instance void [mscorlib]System.IO.TextWriter::WriteLine(string)
IL_0024: ldloc.0
IL_0025: ldc.i4.1
IL_0026: add
IL_0027: stloc.0
IL_0028: ldloc.0
IL_0029: ldc.i4.s 10
IL_002b: blt.s IL_0004
// end loop
IL_002d: ret
} // end of method Program::Main
Run Code Online (Sandbox Code Playgroud)
我也很好奇,看看运行时JITer是否会提升不变量,但它似乎也没有.我将代码更改为以下内容,以使程序集更清晰:
public static void Main()
{
TextWriter cons = Console.Out;
for( int i = 0; i < 10; i++ )
{
cons.WriteLine( i );
cons.WriteLine( Test.Option1.ToString() );
}
}
Run Code Online (Sandbox Code Playgroud)
然后使用VS的调试器来获取程序集,小心确保VS允许JITer进行优化.它仍然没有提升对ToString()的调用:
TextWriter cons = Console.Out;
00000000 push rdi
00000001 push rsi
00000002 sub rsp,28h
00000006 call 0000000050D76460
0000000b mov rsi,rax
for( int i = 0; i < 10; i++ )
0000000e xor edi,edi
{
cons.WriteLine( i );
00000010 mov rcx,rsi
00000013 mov edx,edi
00000015 mov rax,qword ptr [rsi]
00000018 mov rax,qword ptr [rax+60h]
0000001c call qword ptr [rax+28h]
cons.WriteLine( Test.Option1.ToString() );
0000001f mov rcx,7FE90116770h
00000029 call 000000005F6302D0
0000002e mov rcx,rsi
00000031 xor ecx,ecx
00000033 mov dword ptr [rax+8],ecx
00000036 mov rcx,rax
00000039 mov rax,qword ptr [rax]
0000003c mov rax,qword ptr [rax+40h]
00000040 call qword ptr [rax] <---- call System.Enum.ToString()
00000042 mov rdx,rax
00000045 mov rcx,rsi
00000048 mov rax,qword ptr [rsi]
0000004b mov rax,qword ptr [rax+68h]
0000004f call qword ptr [rax+20h]
for( int i = 0; i < 10; i++ )
00000052 inc edi
00000054 cmp edi,0Ah
00000057 jl 0000000000000010
00000059 add rsp,28h
}
}
Run Code Online (Sandbox Code Playgroud)