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

1. 问题再现

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

     public class IL
{
public List<InstanceOne> _instances = new List<InstanceOne>();
public InstanceOne _curInstance;
public Action CallBack; public IL()
{//创建一个用来模拟的List对象
for (int i = ; i < ; i++)
{
_instances.Add(new InstanceOne() { Index = i, Property1 = "Property" + i });
} } public void Test()
{
foreach (var item in _instances)
{
if (item.Index == )
{
Console.WriteLine("Before callback Item index is " + item.Index);
Thread thread = new Thread(CallBackInvoke);
this.CallBack += () =>
{
Console.WriteLine("Current Item index is " + item.Index);
};
thread.Start();
}
}
} public void CallBackInvoke()
{
Thread.Sleep();
if (this.CallBack != null)
this.CallBack();
}
} public class InstanceOne
{
public int Index { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
}

下面是测试调用代码:

         static void Main(string[] args)
{
IL il = new IL();
il.Test();
Console.ReadKey();
}

执行上面的代码之后看看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 反编译结果:

 .method public hidebysig instance void  Test() cil managed
{
// Code size 172 (0xac)
.maxstack
.locals init ([] class [mscorlib]System.Threading.Thread thread,
[] class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegate2',
[] class ILResearch.IL/'<>c__DisplayClass3' 'CS$<>8__locals4',
[] valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne> CS$$)
IL_0000: ldarg.
IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`<class ILResearch.InstanceOne> ILResearch.IL::_instances
IL_0006: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<!> class [mscorlib]System.Collections.Generic.List`<class ILResearch.InstanceOne>::GetEnumerator()
IL_000b: stloc.
.try
{
IL_000c: ldnull
IL_000d: stloc.
IL_000e: newobj instance void ILResearch.IL/'<>c__DisplayClass3'::.ctor()
IL_0013: stloc.
IL_0014: br.s IL_008f
IL_0016: ldloc.
IL_0017: ldloca.s CS$$
IL_0019: call instance ! valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>::get_Current()
IL_001e: stfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_0023: ldloc.
IL_0024: ldfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_0029: callvirt instance int32 ILResearch.InstanceOne::get_Index()
IL_002e: ldc.i4.
IL_002f: bne.un.s IL_008f
IL_0031: ldstr "Before callback Item index is "
IL_0036: ldloc.
IL_0037: ldfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_003c: callvirt instance int32 ILResearch.InstanceOne::get_Index()
IL_0041: box [mscorlib]System.Int32
IL_0046: call string [mscorlib]System.String::Concat(object,
object)
IL_004b: call void [mscorlib]System.Console::WriteLine(string)
IL_0050: ldarg.
IL_0051: ldftn instance void ILResearch.IL::CallBackInvoke()
IL_0057: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object,
native int)
IL_005c: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
IL_0061: stloc.
IL_0062: ldarg.
IL_0063: dup
IL_0064: ldfld class [mscorlib]System.Action ILResearch.IL::CallBack
IL_0069: ldloc.
IL_006a: brtrue.s IL_0079
IL_006c: ldloc.
IL_006d: ldftn instance void ILResearch.IL/'<>c__DisplayClass3'::'<Test>b__1'()
IL_0073: newobj instance void [mscorlib]System.Action::.ctor(object,
native int)
IL_0078: stloc.
IL_0079: ldloc.
IL_007a: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_007f: castclass [mscorlib]System.Action
IL_0084: stfld class [mscorlib]System.Action ILResearch.IL::CallBack
IL_0089: ldloc.
IL_008a: callvirt instance void [mscorlib]System.Threading.Thread::Start()
IL_008f: ldloca.s CS$$
IL_0091: call instance bool valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>::MoveNext()
IL_0096: brtrue IL_0016
IL_009b: leave.s IL_00ab
} // end .try
finally
{
IL_009d: ldloca.s CS$$
IL_009f: constrained. valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>
IL_00a5: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_00aa: endfinally
} // end handler
IL_00ab: ret
} // end of method IL::Test

VS2010 IL

VS 2012的IL 反编译结果:

 .method public hidebysig instance void  Test() cil managed
{
// Code size 175 (0xaf)
.maxstack
.locals init ([] class [mscorlib]System.Threading.Thread thread,
[] class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegate2',
[] class ILResearch.IL/'<>c__DisplayClass3' 'CS$<>8__locals4',
[] valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne> CS$$)
IL_0000: ldarg.
IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`<class ILResearch.InstanceOne> ILResearch.IL::_instances
IL_0006: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<!> class [mscorlib]System.Collections.Generic.List`<class ILResearch.InstanceOne>::GetEnumerator()
IL_000b: stloc.
.try
{
IL_000c: br IL_0092
IL_0011: ldnull
IL_0012: stloc.
IL_0013: newobj instance void ILResearch.IL/'<>c__DisplayClass3'::.ctor()
IL_0018: stloc.
IL_0019: ldloc.
IL_001a: ldloca.s CS$$
IL_001c: call instance ! valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>::get_Current()
IL_0021: stfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_0026: ldloc.
IL_0027: ldfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_002c: callvirt instance int32 ILResearch.InstanceOne::get_Index()
IL_0031: ldc.i4.
IL_0032: bne.un.s IL_0092
IL_0034: ldstr "Before callback Item index is "
IL_0039: ldloc.
IL_003a: ldfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_003f: callvirt instance int32 ILResearch.InstanceOne::get_Index()
IL_0044: box [mscorlib]System.Int32
IL_0049: call string [mscorlib]System.String::Concat(object,
object)
IL_004e: call void [mscorlib]System.Console::WriteLine(string)
IL_0053: ldarg.
IL_0054: ldftn instance void ILResearch.IL::CallBackInvoke()
IL_005a: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object,
native int)
IL_005f: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
IL_0064: stloc.
IL_0065: ldarg.
IL_0066: dup
IL_0067: ldfld class [mscorlib]System.Action ILResearch.IL::CallBack
IL_006c: ldloc.
IL_006d: brtrue.s IL_007c
IL_006f: ldloc.
IL_0070: ldftn instance void ILResearch.IL/'<>c__DisplayClass3'::'<Test>b__1'()
IL_0076: newobj instance void [mscorlib]System.Action::.ctor(object,
native int)
IL_007b: stloc.
IL_007c: ldloc.
IL_007d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0082: castclass [mscorlib]System.Action
IL_0087: stfld class [mscorlib]System.Action ILResearch.IL::CallBack
IL_008c: ldloc.
IL_008d: callvirt instance void [mscorlib]System.Threading.Thread::Start()
IL_0092: ldloca.s CS$$
IL_0094: call instance bool valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>::MoveNext()
IL_0099: brtrue IL_0011
IL_009e: leave.s IL_00ae
} // end .try
finally
{
IL_00a0: ldloca.s CS$$
IL_00a2: constrained. valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>
IL_00a8: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_00ad: endfinally
} // end handler
IL_00ae: ret
} // end of method IL::Test

VS2012 IL

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

 IL_000c:  ldnull
IL_000d: stloc.
IL_000e: newobj instance void ILResearch.IL/'<>c__DisplayClass3'::.ctor()
IL_0013: stloc.
IL_0014: br.s IL_008f
IL_0016: ldloc.
IL_0017: ldloca.s CS$$
IL_0019: call instance ! valuetype [mscorlib]System.Collections.Generic.List`/Enumerator<class ILResearch.InstanceOne>::get_Current()
IL_001e: stfld class ILResearch.InstanceOne ILResearch.IL/'<>c__DisplayClass3'::item
IL_0023: ldloc.
//中间省略
IL_008f: ldloca.s CS$$
IL_0091: call instance bool valuetype [mscorlib]System.Collections.Generic.List`/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方法):

 public unsafe void Test()
{
Thread thread;
Action CS$<>9__CachedAnonymousMethodDelegate2;
<>c__DisplayClass3 CS$<>8__locals4;
List<InstanceOne>.Enumerator CS$$;
CS$$ = this._instances.GetEnumerator();
Label_000C:
try
{
CS$<>9__CachedAnonymousMethodDelegate2 = null;
CS$<>8__locals4 = new <>c__DisplayClass3();
goto Label_008F;
Label_0016:
CS$<>8__locals4.item = &CS$$.Current;
if (CS$<>8__locals4.item.Index != )
{
goto Label_008F;
}
Console.WriteLine("Before callback Item index is " + ((int) CS$<>8__locals4.item.Index));
thread = new Thread(new ThreadStart(this.CallBackInvoke));
if (CS$<>9__CachedAnonymousMethodDelegate2 != null)
{
goto Label_0079;
}
CS$<>9__CachedAnonymousMethodDelegate2 = new Action(CS$<>8__locals4.<Test>b__1);
Label_0079:
this.CallBack = (Action) Delegate.Combine(this.CallBack, CS$<>9__CachedAnonymousMethodDelegate2);
thread.Start();
Label_008F:
if (&CS$$.MoveNext() != null)
{
goto Label_0016;
}
goto Label_00AB;
}
finally
{
Label_009D:
&CS$$.Dispose();
}
Label_00AB:
return;
}

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

 public unsafe void Test()
{
Thread thread;
Action CS$<>9__CachedAnonymousMethodDelegate2;
<>c__DisplayClass3 CS$<>8__locals4;
List<InstanceOne>.Enumerator CS$$;
CS$$ = this._instances.GetEnumerator();
Label_000C:
try
{
goto Label_0092;
Label_0011:
CS$<>9__CachedAnonymousMethodDelegate2 = null;
CS$<>8__locals4 = new <>c__DisplayClass3();
CS$<>8__locals4.item = &CS$$.Current;
if (CS$<>8__locals4.item.Index != )
{
goto Label_0092;
}
Console.WriteLine("Before callback Item index is " + ((int) CS$<>8__locals4.item.Index));
thread = new Thread(new ThreadStart(this.CallBackInvoke));
if (CS$<>9__CachedAnonymousMethodDelegate2 != null)
{
goto Label_007C;
}
CS$<>9__CachedAnonymousMethodDelegate2 = new Action(CS$<>8__locals4.<Test>b__1);
Label_007C:
this.CallBack = (Action) Delegate.Combine(this.CallBack, CS$<>9__CachedAnonymousMethodDelegate2);
thread.Start();
Label_0092:
if (&CS$$.MoveNext() != null)
{
goto Label_0011;
}
goto Label_00AE;
}
finally
{
Label_00A0:
&CS$$.Dispose();
}
Label_00AE:
return;
}

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

3. 总结

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

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

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

    VS2008.  VS2010  . VS2012.  VS2013 都能用的快捷键 Ctrl+E,D             --格式化全部代码 Ctrl+K,F              --格式 ...

  2. vs2010 打开 vs2012 的解决方案

    vs2010 打开 vs2012 的解决方案   vs2012 出来了,但是MS还是一如既往的向下兼容. 废话不多说,直接主题 要使用vs2010打开vs2012的解决方案必须得改2个东西,解决方案 ...

  3. VS2010 打开 VS2012 的项目

    用 VS2010 打开 VS2012 项目,只需两步. 1. 修改解决方案文件(*.sln) 使用记事本打开 *.sln 文件,将里面的 Microsoft Visual Studio Solutio ...

  4. 20140402 cmake编译错误原因 同时装了vs2010和vs2012

    1.cmake编译错误原因 在用cmake编译opencv出现的错误 The CXX compiler identification is MSVC 16.0.30319.1 The C compil ...

  5. VS2010环境下.NET4.0中Tuple<T>的一个小BUG问题

    启动一个桌面程序后,发现一个窗体cfdata=null, 执行时发生错误, 但是在初始化的时候,我明明是cfdata=new Cfdata();为什么会出现这个错误呢. 我开始跟踪,发现当执行cfda ...

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

    我上一遍博文没有通过首页显示!这边就简短的描述一下问题,希望大拿们有遇到类似问题或者知道原因的回答一下下!!! 最终的问题是Loaded事件走两次,具体可以看我上一篇对问题的描述. 在目标框架同样都是 ...

  7. VS2010打开VS2012解决方法

    VS2012中对C#的支持度非常好,不管是编写方便程度(不需要插件就能高亮代码及代码自动提示功能),还对MFC的一些功能优化很多. 我们可以修改两个工程文件来把VS2012的工程文件一直到VS2010 ...

  8. 编写一个小程序,从标准输入读入一系列string对象,寻找连续重复出现的单词。程序应该找出满足一下条件的单词:该单词的后面紧接着再次出现自己本身。跟踪重复次数最多的单词及其重复次数,输出.

    // test13.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> #include< ...

  9. vs2010打开vs2012的sln文件

    1.找到**.sln文件,然后选择用记事本打开. 2.最前面找到“Microsoft Visual Studio Solution File, Format Version 12.00  # Visu ...

随机推荐

  1. SZU:B47 Big Integer II

    Judge Info Memory Limit: 32768KB Case Time Limit: 10000MS Time Limit: 10000MS Judger: Normal Descrip ...

  2. 监听JVM关闭

    使用Runtime的addShutdownHook(thread)方法: for(int i=0; i<5; i++){ System.out.println(i); } Thread th = ...

  3. GMap.Net

    GMap.Net开发之在WinForm和WPF中使用GMap.Net地图插件   GMap.NET是什么? 来看看它的官方说明:GMap.NET is great and Powerful, Free ...

  4. Coursera台大机器学习基础课程1

    Coursera台大机器学习基础课程学习笔记 -- 1 最近在跟台大的这个课程,觉得不错,想把学习笔记发出来跟大家分享下,有错误希望大家指正. 一 机器学习是什么? 感觉和 Tom M. Mitche ...

  5. Caliburn Micro框架

    Caliburn Micro框架快速上手(WP)   一.使用nuget添加起始工程 二.修改App.xaml文件和App.xaml.cs文件 AppBootstrapper介绍: AppBootst ...

  6. [转]在 Mac OS X上编译 libimobiledevice 的方法

    link: http://blog.boceto.fr/2012/05/05/libimobiledevice-for-macosx/ The objective of the day: Compil ...

  7. MKNetworkKit 使用

    关于ios 网络请求之MKNetworkKit库的使用 项目导入MK库之后,还需要导入三个框架文件: SystemConfiguration.framework CFNetwork.framework ...

  8. cocos2d-x C++的do...while(0)另类使用方法

    在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视.      但是 ...

  9. No object in the CompoundRoot has a publicly accessible property named

    No object in the CompoundRoot has a publicly accessible property named 'typeid' (no setter could be ...

  10. 致青春——IT之路

    我的IT青春献给了笔试.面试.人事. 笔试 如果去问一个学生最怕的是什么,或许是考试.参加过高考的都知道,高三过的是什么日子,三天一大考,一天一小考. 当时没觉得什么,因为已经麻木了. 走入职场依然要 ...