.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. 做一个有追求的程序员。

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

时间: 2024-10-01 06:35:47

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

VS2008、 VS2010 、 VS2012、 VS2013 都能用的快捷键

VS2008.  VS2010  . VS2012.  VS2013 都能用的快捷键 Ctrl+E,D             --格式化全部代码 Ctrl+K,F              --格式化选中的代码 CTRL + SHIFT + B          --生成解决方案 CTRL + F7                 --生成编译 CTRL + O                  --打开文件 CTRL + SHIFT + O          --打开项目 CTRL + SH

用VS2010打开VS2012项目

1.修改解决方案文件,即.sln文件: 用记事本打开.sln文件,把其中的 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 修改成 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 2.修改项目文件,即.vcxproj文件: 用记事本打开.vcxproj文件,把两个带有v1

VS2010 打开 VS2012 的项目

用 VS2010 打开 VS2012 项目,只需两步. 1. 修改解决方案文件(*.sln) 使用记事本打开 *.sln 文件,将里面的 Microsoft Visual Studio Solution File, Format Version 12.00# Visual Studio 2012 Project 修改为 Microsoft Visual Studio Solution File, Format Version 11.00# Visual Studio 2010 Project 2

vs2010 和vs2012的区别 副标题--Loaded事件走两次

我上一遍博文没有通过首页显示!这边就简短的描述一下问题,希望大拿们有遇到类似问题或者知道原因的回答一下下!!! 最终的问题是Loaded事件走两次,具体可以看我上一篇对问题的描述. 在目标框架同样都是在.net framework 4.0的情况下,用vs2010和用vs2012或者vs2013的结果不同: 用vs2010,在之前没有安装vs2012的情况下,Loaded事件走两次.安装完vs2012后Loaded事件走一次. 个人理解.net framework 4.0有多个版本,至少vs201

【转】vs2010 打开 vs2012 的解决方案

vs2012 出来了,但是MS还是一如既往的向上兼容. 废话不多说,直接主题 要使用vs2010打开vs2012的解决方案必须得改2个东西,解决方案 和 工程文件 解决方案就是后缀名为 .sln vs2010 vs2012 如图,把Version 从12.00 改为11.00 ,2012 改为 2010 工程文件 即在解决方案里的所有项目文件夹里的后缀名为 .csproj vs2010 vs2012 如图,vs2012的工程文件中多了一行,把多得这行删掉就是,还有特别注意的是要看TargetFr

vs2010 打开 vs2012 的解决方案

vs2012 出来了,但是MS还是一如既往的向下兼容. 废话不多说,直接主题 要使用vs2010打开vs2012的解决方案必须得改2个东西,解决方案 和 工程文件 解决方案就是后缀名为 .sln vs2010 vs2012 如图,把Version 从12.00 改为11.00 ,2012 改为 2010 工程文件 即在解决方案里的所有项目文件夹里的后缀名为 .csproj vs2010 vs2012 如图,vs2012的工程文件中多了一行,把多得这行删掉就是,还有特别注意的是要看TargetFr

快速排序(拔萝卜法,实质就是一直否定寻找数当前的位置,一个萝卜,一个坑)

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace QuickSort{    class Program    {        static void Main(string[] args)        {            List<int> list = new List<int>

VS2010环境下.NET4.0中Tuple&lt;T&gt;的一个小BUG问题

启动一个桌面程序后,发现一个窗体cfdata=null, 执行时发生错误, 但是在初始化的时候,我明明是cfdata=new Cfdata();为什么会出现这个错误呢. 我开始跟踪,发现当执行cfdata=new Cfdata()时, 执行被中断, 后面的代码没有执行, 但是VS2010没有报错, 也就是执行到cfdata=new Cfdata()时发生了错误!但是VS2010没有提示我! 继续调试跟踪,问题出现了以下代码,使用了未赋值的Tuple<T1,T2>的成员值, 而VS2010并没有

【Android小项目】找不同,改编自&amp;quot;寻找房祖名&amp;quot;的一款开源小应用。

近期在微信朋友圈"寻找房祖名"和"万里寻刀"这类小游戏比較火.我试着写了一个android版本号的,里面全是一系列的形近字,实现原理非常easy:用一个GridView然后每一项做成正方形的就可以,点击到正确项改变GridView的行数和列数就可以. 一. 游戏说明: 找不同.一款区分形近字的Androidclient软件,旨在帮助用户认识和区分形近字: 二.游戏规则: 1.指定时间内通关数越多积分越高. 三.截图: 项目地址:FindDifferent