C#编译器奇怪与委托构造函数

时间:2011-11-10 09:58:36

标签: c# compiler-construction delegates constructor

基于following question,我发现了c#编译器的一些奇怪的行为。

以下是有效的C#:

static void K() {}

static void Main()
{
  var k = new Action(new Action(new Action(K))));
}

我觉得奇怪的是编译器'解构'传递的委托。

ILSpy输出如下:

new Action(new Action(new Action(null, ldftn(K)), ldftn(Invoke)).Invoke);

可以看出,它会自动决定使用委托的Invoke方法。但为什么呢?

实际上,代码不清楚。我们是否有一个三重包装的代表(实际)或者是内部代表只是'复制'到外部代表(我最初的想法)。

当然,如果意图就像编译器发出代码一样,那么应该写一下:

var k = new Action(new Action(new Action(K).Invoke).Invoke);

与反编译代码类似。

有人可以证明这种“令人惊讶的”转变的原因吗?

更新

我只能想到一个可能的用例;委托类型转换。例如:

delegate void Baz();
delegate void Bar();
...
var k = new Baz(new Bar( new Action (K)));

如果使用相同的委托类型,编译器可能会发出警告。

3 个答案:

答案 0 :(得分:5)

规范(第7.6.10.5节)说:

  
      
  • 使用与E给出的委托实例相同的调用列表初始化新的委托实例。
  •   

现在假设编译器将其翻译成类似于你的建议:

new Action( a.Target, a.Method)

那只会创建一个带有单个方法调用的调用列表的委托。对于多播代表,它会违反规范。

示例代码:

using System;

class Program
{
    static void Main(string[] args)
    {
        Action first = () => Console.WriteLine("First");
        Action second = () => Console.WriteLine("Second");

        Action both = first + second;
        Action wrapped1 =
            (Action) Delegate.CreateDelegate(typeof(Action),
                                             both.Target, both.Method);
        Action wrapped2 = new Action(both);

        Console.WriteLine("Calling wrapped1:");
        wrapped1();

        Console.WriteLine("Calling wrapped2:");
        wrapped2();
    }
}

输出:

Calling wrapped1:
Second
Calling wrapped2:
First
Second

正如您所看到的,编译器的真实行为符合规范 - 您建议的行为不符合。

部分原因在于Delegate有点奇怪的“有时是单演,有时是多演员”,当然......

答案 1 :(得分:3)

当您尝试将委托视为方法时,编译器实际上使用委托的Invoke()方法。因此,例如,下面的两行编译为完全相同的IL(都调用Invoke()):

k();
k.Invoke();

我认为你看到的奇怪之处就是这样。委托构造函数需要一个方法(或者更确切地说,一个方法组),但它会获得一个委托。因此,它将其视为一种方法,并使用Invoke()方法。

至于含义,它是调用委托调用实际方法的委托。您可以通过访问代理人的MethodTarget属性来自行验证。对于最外层代表,MethodAction.InvokeTarget为内部代表。

答案 2 :(得分:2)

  • 委托是一个类
  • 动作委托有一个像这样的构造函数

    public extern Action(object @object,IntPtr method);

  • 由于K是静态方法,因此不需要将对象作为第一个参数传递给最内部的动作实例,因此它传递null

  • 由于第二个参数是指向函数的指针,因此它使用ldftn函数
  • 传递K方法的指针
  • 对于剩余的Action实例,传递对象是内部Action,第二个参数是Invoke方法,因为当您调用委托时实际上是调用Invoke方法

摘要

var action = new Action(K) => Action action = new Action(null, ldftn(K))
new Action(action) => new Action(action, ldftn(Action.Invoke))

我希望这可以解释发生了什么?