.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 InstanceOne
42     {
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.0
10   IL_0001:  ldfld      class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne> ILResearch.IL::_instances
11   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.3
13   .try
14   {
15     IL_000c:  ldnull
16     IL_000d:  stloc.1
17     IL_000e:  newobj     instance void ILResearch.IL/'<>c__DisplayClass3'::.ctor()
18     IL_0013:  stloc.2
19     IL_0014:  br.s       IL_008f
20     IL_0016:  ldloc.2
21     IL_0017:  ldloca.s   CS$5$0000
22     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'::item
24     IL_0023:  ldloc.2
25     IL_0024:  ldfld      class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
26     IL_0029:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()
27     IL_002e:  ldc.i4.1
28     IL_002f:  bne.un.s   IL_008f
29     IL_0031:  ldstr      "Before callback Item index is "
30     IL_0036:  ldloc.2
31     IL_0037:  ldfld      class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
32     IL_003c:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()
33     IL_0041:  box        [mscorlib]System.Int32
34     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.0
38     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.0
43     IL_0062:  ldarg.0
44     IL_0063:  dup
45     IL_0064:  ldfld      class [mscorlib]System.Action ILResearch.IL::CallBack
46     IL_0069:  ldloc.1
47     IL_006a:  brtrue.s   IL_0079
48     IL_006c:  ldloc.2
49     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.1
53     IL_0079:  ldloc.1
54     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.Action
57     IL_0084:  stfld      class [mscorlib]System.Action ILResearch.IL::CallBack
58     IL_0089:  ldloc.0
59     IL_008a:  callvirt   instance void [mscorlib]System.Threading.Thread::Start()
60     IL_008f:  ldloca.s   CS$5$0000
61     IL_0091:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()
62     IL_0096:  brtrue     IL_0016
63     IL_009b:  leave.s    IL_00ab
64   }  // end .try
65   finally
66   {
67     IL_009d:  ldloca.s   CS$5$0000
68     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:  endfinally
71   }  // end handler
72   IL_00ab:  ret
73 } // 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.0
10   IL_0001:  ldfld      class [mscorlib]System.Collections.Generic.List`1<class ILResearch.InstanceOne> ILResearch.IL::_instances
11   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.3
13   .try
14   {
15     IL_000c:  br         IL_0092
16     IL_0011:  ldnull
17     IL_0012:  stloc.1
18     IL_0013:  newobj     instance void ILResearch.IL/'<>c__DisplayClass3'::.ctor()
19     IL_0018:  stloc.2
20     IL_0019:  ldloc.2
21     IL_001a:  ldloca.s   CS$5$0000
22     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'::item
24     IL_0026:  ldloc.2
25     IL_0027:  ldfld      class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
26     IL_002c:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()
27     IL_0031:  ldc.i4.1
28     IL_0032:  bne.un.s   IL_0092
29     IL_0034:  ldstr      "Before callback Item index is "
30     IL_0039:  ldloc.2
31     IL_003a:  ldfld      class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
32     IL_003f:  callvirt   instance int32 ILResearch.InstanceOne::get_Index()
33     IL_0044:  box        [mscorlib]System.Int32
34     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.0
38     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.0
43     IL_0065:  ldarg.0
44     IL_0066:  dup
45     IL_0067:  ldfld      class [mscorlib]System.Action ILResearch.IL::CallBack
46     IL_006c:  ldloc.1
47     IL_006d:  brtrue.s   IL_007c
48     IL_006f:  ldloc.2
49     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.1
53     IL_007c:  ldloc.1
54     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.Action
57     IL_0087:  stfld      class [mscorlib]System.Action ILResearch.IL::CallBack
58     IL_008c:  ldloc.0
59     IL_008d:  callvirt   instance void [mscorlib]System.Threading.Thread::Start()
60     IL_0092:  ldloca.s   CS$5$0000
61     IL_0094:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ILResearch.InstanceOne>::MoveNext()
62     IL_0099:  brtrue     IL_0011
63     IL_009e:  leave.s    IL_00ae
64   }  // end .try
65   finally
66   {
67     IL_00a0:  ldloca.s   CS$5$0000
68     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:  endfinally
71   }  // end handler
72   IL_00ae:  ret
73 } // 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     try
10     {
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     finally
38     {
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     try
10     {
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     finally
38     {
39     Label_00A0:
40         &CS$5$0000.Dispose();
41     }
42 Label_00AE:
43     return;
44 }

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

3. 总结

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

 

posted @ 2014-06-16 21:33  仰望星辰  阅读(1088)  评论(0编辑  收藏  举报