6

我一直在使用LINQPad中的一些 C# 语句,以了解发出了什么中间语言代码。

我首先尝试了以下代码:

var Container = new {Name = "James"};
Console.WriteLine(Container.Name);

并看到以下六行 IL 发出:

IL_0001:  ldstr       "James"
IL_0006:  newobj      <>f__AnonymousType0<System.String>..ctor
IL_000B:  stloc.0     
IL_000C:  ldloc.0     
IL_000D:  callvirt    <>f__AnonymousType0<System.String>.get_Name
IL_0012:  call        System.Console.WriteLine

这大致是我所期望的,并且很好地展示了匿名类型如何是只读/不可变的,因为没有 set_Name 属性。

接下来我尝试了以下语句:

dynamic Container = new System.Dynamic.ExpandoObject();
Container.Name = "James";
Console.WriteLine(Container.Name);

这会导致大量的 IL 被排放。我不会在这里粘贴它,但你可以在这个 pastebin中找到它。

我知道在管理动态类型和 ExpandoObject 方面有相当多的开销,但我不明白为什么在这种情况下调用似乎System.Console.WriteLine是通过内部反射执行的。

IL_0072:  ldstr       "WriteLine"
....
IL_00BF:  ldtoken     System.Console

在第一段代码中,在检索和存储属性之后,它是一个单行 IL 语句,它调用了System.Console.WriteLine.

那么为什么使用dynamic类型的调用需要所有这些额外的东西呢?

4

2 回答 2

7

因为变量是dynamic没有办法知道的,在编译时WriteLine应该调用哪个重载。直到运行时我们才知道dynamic对象的实际类型。由于工作方式dynamic的原因,重要的是它不仅仅被视为object编译时;部分功能是它在运行时确定正确的过载。

如果您将对象转换为动态以外的其他对象(即string在调用之后ToString或刚刚返回ExpandoObject)然后将其传递给,WriteLine那么您应该会看到反射调用消失并看到它在编译时静态确定WriteLine.

于 2012-09-20T19:55:18.987 回答
1

正在发生的事情是编译器正在以可以“后期绑定”的方式创建您的代码。后期绑定意味着与传统数据类型和对象一样,不是在编译期间解析对象,而是在运行时解析对象,而程序集实际上在内存中并正在运行。

如果你在 Reflector 或 dotPeek 中查看你的代码,你会看到你的动态对象被一个[Dynamic]属性修饰了。当你的程序在内存中运行时,当涉及到一个用这个属性修饰的对象时,对这个对象的调用是通过一个动态的Container(或者你的对象被调用的任何东西)进行的。这Container是由负责运行时绑定的 Binder 初始化的。这就是所有被调用者Microsoft.CSharp.RuntimeBinder所做的。这RuntimeBinder稍后用于调用属性或方法或任何动态的。

我希望这能澄清一点。我在我的安卓上打字,所以解释可能不太理想。我稍后会清理它。

于 2012-09-20T20:12:27.823 回答