首页 > 代码库 > .net: 不能忽视的break——寻找VS2010和VS2012编译器的一个小区别

.net: 不能忽视的break——寻找VS2010和VS2012编译器的一个小区别

 

文中的问题来自于实际开发,但是实际开发中的代码逻辑比较复杂,因此下面的代码去掉了所有逻辑,只保留能体现问题的代码,类和都只为了说明问题,并不具有实际意义。下面首先看看下面的代码和现象。

1. 问题再现

下面的代码重现了场景, 看完这段代码是不有任何问题吗?下面看看输出结果。

 

 1     public class IL 2     { 3         public List<InstanceOne> _instances = new List<InstanceOne>(); 4         public InstanceOne _curInstance; 5         public Action CallBack; 6  7         public IL() 8         {//创建一个用来模拟的List对象 9             for (int i = 0; i < 5; i++)10             {11                 _instances.Add(new InstanceOne() { Index = i, Property1 = "Property" + i });12             }13 14         }15 16         public void Test()17         {18             foreach (var item in _instances)19             {20                 if (item.Index == 1)21                 {22                     Console.WriteLine("Before callback Item index is " + item.Index);23                     Thread thread = new Thread(CallBackInvoke);24                     this.CallBack += () =>25                     {26                         Console.WriteLine("Current Item index is " + item.Index);27                     };28                     thread.Start();29                 }30             }31         }32 33         public void CallBackInvoke()34         {35             Thread.Sleep(1000);36             if (this.CallBack != null)37                 this.CallBack();38         }39     }40 41     public class InstanceOne42     {43         public int Index { get; set; }44         public string Property1 { get; set; }45         public string Property2 { get; set; }46     }

 

下面是测试调用代码:

1         static void Main(string[] args)2         {3             IL il = new IL();4             il.Test();5             Console.ReadKey();6         }

执行上面的代码之后看看2012的输出结果:

 

我们在Callback回调中使用的对象还是Index等于1的对象,执行结果并没有什么不妥。但是大家不要以为这样就万事大吉了。我们的代码是在VS2012中开发的,但是别人没有VS2012,只有VS2010,所以代码需要在VS2010中编译和调试。但是当代码在VS2010中调试时很奇怪的一幕发生了!!为什么,看看下面的输出结果。

我们在Callback回调里拿到的对象不是我们想象的Index等于1.而是等于4的对象。同样的代码为啥呢?同样的代码,不同的结果,可能程序员第一反应都是我的代码没变,就是没问题。好吧,无论如何,这依然是需要我们去深究的问题。这样来证明我们的代码是没问题还是有问题。

注:上面的问题是通过debug发现使用的对象并不是想要的index==1,而是index==4. 这个代码中的输出只为体现对象不同而输出的。

 

2. 代码分析

代码相同,但是结果却大相近庭,这促使我们不得不一探究竟。但是到底是为什么呢?嗯。。。?对,他们是VS2010和VS2012的编译的不同版本,会不会是他们不同导致的结果?那怎么判断是不是VS不同版本之间的问题呢?对,想到了IL反编译,下面列出VS2010和VS2012的对方法Test()的反编译结果,然后我们来一起看看他们到底在编译时编译器做了什么导致他们会有不同的结果(关于如何反编译,可以使用微软自带的工具ildasm.exe)。

VS 2010的IL 反编译结果:

 1 .method public hidebysig instance void  Test() cil managed 2 { 3   // Code size       172 (0xac) 4   .maxstack  5 5   .locals init ([0] class [mscorlib]System.Threading.Thread thread, 6            [1] class [mscorlib]System.Action CS$<>9__CachedAnonymousMethodDelegate2, 7            [2] class ILResearch.IL/<>c__DisplayClass3 CS$<>8__locals4, 8            [3] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne> CS$5$0000) 9   IL_0000:  ldarg.010   IL_0001:  ldfld      class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne> ILResearch.IL::_instances11   IL_0006:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne>::GetEnumerator()12   IL_000b:  stloc.313   .try14   {15     IL_000c:  ldnull16     IL_000d:  stloc.117     IL_000e:  newobj     instance void ILResearch.IL/<>c__DisplayClass3::.ctor()18     IL_0013:  stloc.219     IL_0014:  br.s       IL_008f20     IL_0016:  ldloc.221     IL_0017:  ldloca.s   CS$5$000022     IL_0019:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::get_Current()23     IL_001e:  stfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item24     IL_0023:  ldloc.225     IL_0024:  ldfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item26     IL_0029:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()27     IL_002e:  ldc.i4.128     IL_002f:  bne.un.s   IL_008f29     IL_0031:  ldstr      "Before callback Item index is "30     IL_0036:  ldloc.231     IL_0037:  ldfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item32     IL_003c:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()33     IL_0041:  box        [mscorlib]System.Int3234     IL_0046:  call       string [mscorlib]System.String::Concat(object,35                                                                 object)36     IL_004b:  call       void [mscorlib]System.Console::WriteLine(string)37     IL_0050:  ldarg.038     IL_0051:  ldftn      instance void ILResearch.IL::CallBackInvoke()39     IL_0057:  newobj     instance void [mscorlib]System.Threading.ThreadStart::.ctor(object,40                                                                                      native int)41     IL_005c:  newobj     instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)42     IL_0061:  stloc.043     IL_0062:  ldarg.044     IL_0063:  dup45     IL_0064:  ldfld      class [mscorlib]System.Action ILResearch.IL::CallBack46     IL_0069:  ldloc.147     IL_006a:  brtrue.s   IL_007948     IL_006c:  ldloc.249     IL_006d:  ldftn      instance void ILResearch.IL/<>c__DisplayClass3::<Test>b__1()50     IL_0073:  newobj     instance void [mscorlib]System.Action::.ctor(object,51                                                                       native int)52     IL_0078:  stloc.153     IL_0079:  ldloc.154     IL_007a:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,55                                                                                             class [mscorlib]System.Delegate)56     IL_007f:  castclass  [mscorlib]System.Action57     IL_0084:  stfld      class [mscorlib]System.Action ILResearch.IL::CallBack58     IL_0089:  ldloc.059     IL_008a:  callvirt   instance void [mscorlib]System.Threading.Thread::Start()60     IL_008f:  ldloca.s   CS$5$000061     IL_0091:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()62     IL_0096:  brtrue     IL_001663     IL_009b:  leave.s    IL_00ab64   }  // end .try65   finally66   {67     IL_009d:  ldloca.s   CS$5$000068     IL_009f:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>69     IL_00a5:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()70     IL_00aa:  endfinally71   }  // end handler72   IL_00ab:  ret73 } // end of method IL::Test
VS2010 IL

VS 2012的IL 反编译结果:

 1 .method public hidebysig instance void  Test() cil managed 2 { 3   // Code size       175 (0xaf) 4   .maxstack  4 5   .locals init ([0] class [mscorlib]System.Threading.Thread thread, 6            [1] class [mscorlib]System.Action CS$<>9__CachedAnonymousMethodDelegate2, 7            [2] class ILResearch.IL/<>c__DisplayClass3 CS$<>8__locals4, 8            [3] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne> CS$5$0000) 9   IL_0000:  ldarg.010   IL_0001:  ldfld      class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne> ILResearch.IL::_instances11   IL_0006:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne>::GetEnumerator()12   IL_000b:  stloc.313   .try14   {15     IL_000c:  br         IL_009216     IL_0011:  ldnull17     IL_0012:  stloc.118     IL_0013:  newobj     instance void ILResearch.IL/<>c__DisplayClass3::.ctor()19     IL_0018:  stloc.220     IL_0019:  ldloc.221     IL_001a:  ldloca.s   CS$5$000022     IL_001c:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::get_Current()23     IL_0021:  stfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item24     IL_0026:  ldloc.225     IL_0027:  ldfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item26     IL_002c:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()27     IL_0031:  ldc.i4.128     IL_0032:  bne.un.s   IL_009229     IL_0034:  ldstr      "Before callback Item index is "30     IL_0039:  ldloc.231     IL_003a:  ldfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item32     IL_003f:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()33     IL_0044:  box        [mscorlib]System.Int3234     IL_0049:  call       string [mscorlib]System.String::Concat(object,35                                                                 object)36     IL_004e:  call       void [mscorlib]System.Console::WriteLine(string)37     IL_0053:  ldarg.038     IL_0054:  ldftn      instance void ILResearch.IL::CallBackInvoke()39     IL_005a:  newobj     instance void [mscorlib]System.Threading.ThreadStart::.ctor(object,40                                                                                      native int)41     IL_005f:  newobj     instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)42     IL_0064:  stloc.043     IL_0065:  ldarg.044     IL_0066:  dup45     IL_0067:  ldfld      class [mscorlib]System.Action ILResearch.IL::CallBack46     IL_006c:  ldloc.147     IL_006d:  brtrue.s   IL_007c48     IL_006f:  ldloc.249     IL_0070:  ldftn      instance void ILResearch.IL/<>c__DisplayClass3::<Test>b__1()50     IL_0076:  newobj     instance void [mscorlib]System.Action::.ctor(object,51                                                                       native int)52     IL_007b:  stloc.153     IL_007c:  ldloc.154     IL_007d:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,55                                                                                             class [mscorlib]System.Delegate)56     IL_0082:  castclass  [mscorlib]System.Action57     IL_0087:  stfld      class [mscorlib]System.Action ILResearch.IL::CallBack58     IL_008c:  ldloc.059     IL_008d:  callvirt   instance void [mscorlib]System.Threading.Thread::Start()60     IL_0092:  ldloca.s   CS$5$000061     IL_0094:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()62     IL_0099:  brtrue     IL_001163     IL_009e:  leave.s    IL_00ae64   }  // end .try65   finally66   {67     IL_00a0:  ldloca.s   CS$5$000068     IL_00a2:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>69     IL_00a8:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()70     IL_00ad:  endfinally71   }  // end handler72   IL_00ae:  ret73 } // end of method IL::Test
VS2012 IL

 

问题主要出现在foreach语句中,因此我们对反编译语言只关心foreach的部分,但是为了表达完整性,其他部分也保留。经过分析,最后发现VS2010中在循环体开始时的指令是这么写的:

 IL_000c:  ldnull    IL_000d:  stloc.1    IL_000e:  newobj     instance void ILResearch.IL/<>c__DisplayClass3::.ctor()    IL_0013:  stloc.2    IL_0014:  br.s       IL_008f    IL_0016:  ldloc.2    IL_0017:  ldloca.s   CS$5$0000    IL_0019:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::get_Current()    IL_001e:  stfld      class ILResearch.InstanceOne ILResearch.IL/<>c__DisplayClass3::item    IL_0023:  ldloc.2//中间省略    IL_008f:  ldloca.s   CS$5$0000    IL_0091:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()    IL_0096:  brtrue     IL_0016    IL_009b:  leave.s    IL_00ab

而VS2012是的指令是这样:

IL_000c: br IL_0092
IL_0011: ldnull
IL_0012: stloc.1
IL_0013: newobj instance void ILResearch.IL/‘<>c__DisplayClass3‘::.ctor()
IL_0018: stloc.2
IL_0019: ldloc.2
IL_001a: ldloca.s CS$5$0000
//中间省略。。。
IL_0092: ldloca.s CS$5$0000
IL_0094: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()
IL_0099: brtrue IL_0011
IL_009e: leave.s IL_00ae

下面我们分析一下,相信很多童鞋们马上就明白了,VS2010在循环体开始时,首先创建临时对象c__DisplayClass3(指令 IL_0013),然后再指令IL_0016和IL_008f 之间做循环遍历list。VS2010只创建过一次c__DisplayClass3对象,每次循环时IL_001e修改c__DisplayClass3的属性Item,所以在因此只存在一个c__DisplayClass3对象,所以callback中拿到的对象属性Item已经被修改了。

但是vs2012完全不同,他做循环是在指令IL_0011 和 IL_0092中间,每次创建一个c__DisplayClass3对象,每个对象都有自己的属性Item,并且callback回调事件也是c__DisplayClass3的一个属性,因此callback拿到的item是自己独有的,没有被修改。

但是对于这么样的IL语言对于我们地球人看起来很费劲,至于我是怎么看出这个问题原因的,是从博客园老赵的博客中有一篇用Reflector查看不使用Optimize选项查看代码的文章中得到启发,用Reflector查看了反编译代码:

这个是Reflector反编译的VS2010的代码(Test方法):

 1 public unsafe void Test() 2 { 3     Thread thread; 4     Action CS$<>9__CachedAnonymousMethodDelegate2; 5     <>c__DisplayClass3 CS$<>8__locals4; 6     List<InstanceOne>.Enumerator CS$5$0000; 7     CS$5$0000 = this._instances.GetEnumerator(); 8 Label_000C: 9     try10     {11         CS$<>9__CachedAnonymousMethodDelegate2 = null;12         CS$<>8__locals4 = new <>c__DisplayClass3();13         goto Label_008F;14     Label_0016:15         CS$<>8__locals4.item = &CS$5$0000.Current;16         if (CS$<>8__locals4.item.Index != 1)17         {18             goto Label_008F;19         }20         Console.WriteLine("Before callback Item index is " + ((int) CS$<>8__locals4.item.Index));21         thread = new Thread(new ThreadStart(this.CallBackInvoke));22         if (CS$<>9__CachedAnonymousMethodDelegate2 != null)23         {24             goto Label_0079;25         }26         CS$<>9__CachedAnonymousMethodDelegate2 = new Action(CS$<>8__locals4.<Test>b__1);27     Label_0079:28         this.CallBack = (Action) Delegate.Combine(this.CallBack, CS$<>9__CachedAnonymousMethodDelegate2);29         thread.Start();30     Label_008F:31         if (&CS$5$0000.MoveNext() != null)32         {33             goto Label_0016;34         }35         goto Label_00AB;36     }37     finally38     {39     Label_009D:40         &CS$5$0000.Dispose();41     }42 Label_00AB:43     return;44 }

这个是reflector反编译的VS2012的代码:

 1 public unsafe void Test() 2 { 3     Thread thread; 4     Action CS$<>9__CachedAnonymousMethodDelegate2; 5     <>c__DisplayClass3 CS$<>8__locals4; 6     List<InstanceOne>.Enumerator CS$5$0000; 7     CS$5$0000 = this._instances.GetEnumerator(); 8 Label_000C: 9     try10     {11         goto Label_0092;12     Label_0011:13         CS$<>9__CachedAnonymousMethodDelegate2 = null;14         CS$<>8__locals4 = new <>c__DisplayClass3();15         CS$<>8__locals4.item = &CS$5$0000.Current;16         if (CS$<>8__locals4.item.Index != 1)17         {18             goto Label_0092;19         }20         Console.WriteLine("Before callback Item index is " + ((int) CS$<>8__locals4.item.Index));21         thread = new Thread(new ThreadStart(this.CallBackInvoke));22         if (CS$<>9__CachedAnonymousMethodDelegate2 != null)23         {24             goto Label_007C;25         }26         CS$<>9__CachedAnonymousMethodDelegate2 = new Action(CS$<>8__locals4.<Test>b__1);27     Label_007C:28         this.CallBack = (Action) Delegate.Combine(this.CallBack, CS$<>9__CachedAnonymousMethodDelegate2);29         thread.Start();30     Label_0092:31         if (&CS$5$0000.MoveNext() != null)32         {33             goto Label_0011;34         }35         goto Label_00AE;36     }37     finally38     {39     Label_00A0:40         &CS$5$0000.Dispose();41     }42 Label_00AE:43     return;44 }

从这个代码中和容易看出上面的问题,如果reflector optimize选项不选择为None是不能看出这个区别的,只能看到原代码。

3. 总结

 通过上面的问题学会了很多方法,但是在平时学代码是还是需要写上break. 做一个有追求的程序员。