技术开发 频道

IL可以看到的东西,其实大都也可以用C#来发现

 【IT168技术文档】

转载自Jeff Zhao的博客

    在上一篇文章中,我们通过一些示例谈论了IL与CLR中的一些特性。IL与C#等高级语言的作用类似,主要用于表示程序的逻辑。由于它同样了解太多CLR中的高级特性,因此它在大部分情况下依旧无法展现出比那些高级语言更多的CLR细节。因此,如果您想要通过学习IL来了解CLR,那么这个过程很可能会“事倍功半”。因此,从这个角度来说,老赵并不倾向于学习IL。不过严格说来,即使IL无法看出CLR的细节,也不足以说明“IL无用”——这里说“无用”自然有些夸张。但是,如果我们还发现,那些原本被认为需要通过IL挖掘到的东西,现在都可以使用更好的方法来获得,并且可以起到“事半功倍”的效果,那么似乎我们真的没有太多理由去追逐IL了。

 在这篇文章中,我们使用最多的工具便是.NET Reflector,从.NET 1.x开始,.NET Reflector就是一个探究.NET框架(主要是BCL)内部实现的有力工具,它可以把一个程序集高度还原成C#等高级语言的代码。在它的帮助下,几乎所有程序集实现都变得一目了然,这大大方便了我们的工作。老赵对此深有感触,因为在某段不算短的时间内,我使用.NET Reflector阅读过的代码数量远远超过了自己编写的代码。与此相反的是,老赵几乎没有使用IL探索过.NET框架下的任何问题。这可能还涉及到方式方法和个人做事方式,但是如果这真有效果的话,为什么要舍近求远呢?希望您看过了这篇文章,也可以像我一样摆脱IL,投入.NET Reflector的怀抱。

 示例一:探究语言细节

 C#语言从1.0到3.0版本的进化过程中,大部分新特性都是依靠编译器的魔法。就拿C#3.0的各种新特性来说,Lambda表达式,LINQ,自动属性等等,完全都是基于CLR 2.0中已有的功能,再配合新的C#编译器而产生的各种神奇效果。有些朋友认为,掌握IL之后便把握了.NET的根本,以不变应万变,只要读懂IL,那么这些新特性都不会对您形成困扰。这话说的并没有错,只是老赵认为,“掌握IL”在这里只是一个“充分条件”而不是一个“必要条件”,我们完全可以使用.NET Reflector将程序集反编译成C#代码来观察这些。

 这里我们使用.NET Reflector来观察最最常见,最最普通的foreach关键字的功能。我们都知道foreach是遍历一个IEnumerble对象内元素的方式,我们也都知道foreach其实是GoF Iterator模式的实现,通过MoveNext方法和Current属性进行配合共同完成。不过大部分朋友似乎都是从IL进行观察,或是“听别人说”而了解这些的。事实上,.NET Reflector也可以很容易地证实这一点,只是这中间还有些“特别”的地方。那么首先,我们还是来准备一个最简单的foreach语句: 

static void DoEnumerable(IEnumerable<int> source)

 {

 foreach (int i in source)

 {

 Console.WriteLine(i);

 }

 }

 如果观察它的IL代码,即使不了解IL的朋友也一定可以看出,其中涉及到了GetEnumerator,MoveNext和Current等成员的访问:

.method private hidebysig static void DoEnumerable(

 class [mscorlib]System.Collections.Generic.IEnumerable`1 source) cil managed

 {

 .maxstack 1

 .locals init (

 [0] int32 i,

 [1] class [mscorlib]System.Collections.Generic.IEnumerator`1 CS$5$0000)

 L_0000: ldarg.0

 L_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1

 [mscorlib]System.Collections.Generic.IEnumerable`1::GetEnumerator()

 L_0006: stloc.1

 L_0007: br.s L_0016

 L_0009: ldloc.1

 L_000a: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1::get_Current()

 L_000f: stloc.0

 L_0010: ldloc.0

 L_0011: call void [mscorlib]System.Console::WriteLine(int32)

 L_0016: ldloc.1

 L_0017: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()

 L_001c: brtrue.s L_0009

 L_001e: leave.s L_002a

 L_0020: ldloc.1

 L_0021: brfalse.s L_0029

 L_0023: ldloc.1

 L_0024: callvirt instance void [mscorlib]System.IDisposable::Dispose()

 L_0029: endfinally

 L_002a: ret

 .try L_0007 to L_0020 finally handler L_0020 to L_002a

 }

 但是,如果使用.NET Reflector观察它的C#代码又会如何呢?

 private static void DoEnumerable(IEnumerable source)

 {

 foreach (int i in source)

 {

 Console.WriteLine(i);

 }

 }

 请注意,以上这段是由.NET Reflector从IL反编译后得到的C#代码,这简直……不是简直,是完完全全真真正正地和我们刚才写的代码一模一样!这就是.NET Reflector的强大之处,由于它意识到IL调用了IEnumerable<T>.GetEnumerator方法,因此它就“留心”判断这个调用的“模式”是否符合一个标准的foreach,如果是,那么就会显示为一个foreach语句而不是while...MoveNext。不过,这难道不就掩盖了“事物本质”了吗?要知道我们的目标可是探究foreach的形式,既然.NET Reflector帮不了的话,我们不还是需要去观察IL吗?

 刚才老赵提到,.NET Reflector在判断IL代码时发现一些标准的模式时会进行代码“优化”。那么我们能否让.NET Reflector不要做这种“优化”呢?答案是肯定的,只是需要您在.NET Reflector中进行一些简单的设置:

0
相关文章