具有委托构造函数的C#编译器奇点

具有委托构造函数的C#编译器奇点,c#,compiler-construction,delegates,constructor,C#,Compiler Construction,Delegates,Constructor,基于这一点,我发现c#编译器有一些奇怪的行为 以下是有效的C#: 我确实觉得奇怪的是编译器“解构”了传递的委托 ILSpy的输出如下所示: new Action(new Action(new Action(null, ldftn(K)), ldftn(Invoke)).Invoke); 可以看到,它自动决定使用委托的Invoke方法。但是为什么呢 事实上,代码并不清楚。我们是否有一个三重包装的委托(实际的),或者内部委托只是“复制”到外部委托(我最初的想法) 当然,如果意图与编译器发出代码类似

基于这一点,我发现c#编译器有一些奇怪的行为

以下是有效的C#:

我确实觉得奇怪的是编译器“解构”了传递的委托

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)));
如果使用相同的委托类型,编译器可能会发出警告。

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

    公共外部操作(object@object,IntPtr方法)

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

  • 因为第二个参数是指向函数的指针,所以它使用ldftn函数传递K方法的指针
  • 对于其余的操作实例,传递的对象是内部操作,第二个参数是Invoke方法,因为调用委托时实际上是调用Invoke方法
总结

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

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

当您试图将委托作为一种方法处理时,编译器实际上使用委托的
Invoke()
方法。例如,下面的两行编译为完全相同的IL(都调用
Invoke()
):

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

至于含义,是delegate调用delegate,delegate调用实际的方法。您可以通过访问代理的
方法
目标
属性来验证这一点。对于最外层的委托,
方法是
操作。调用
目标
内部委托。

规范(第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
如您所见,编译器的实际行为与规范相匹配——您建议的行为与规范不匹配


这在一定程度上是由于
代理的“有时是单播,有时是多播”性质有些奇怪,当然……

不清楚您所说的“内部代理只是“复制”到外部代理”是什么意思-您到底期望什么?单个委托实例?出于兴趣,您会在哪里使用这样的构造?看起来“三层包装的委托”会吃掉不精明的程序员作为早餐…@JonSkeet:我希望它更类似于
新动作(a.Target,a.Method)
,而不是
新动作(a.Invoke)
。瞧,不要包装。@Killercam:这是终极问题!为什么C#编译器会邀请这样的代码。@Killercam:我永远不会写这样的代码,事实上,我认为这是编译器的错误。令我大吃一惊的是,事实并非如此。目前,我只能想到一个用例,但这涉及到使用不同的委托类型,因此充当委托类型转换器。我问的问题正是您在最后一点中所说的。我知道发生了什么,但为什么?如果你再看一遍我的问题,你会注意到你所说的一切都已经存在了。我理解正在发生的事情。我想知道这一点的理由(或原因)是什么。@leppie编译器还应该如何转换您的代码?MSIL中没有新操作(blah)之类的东西。C只提供合成糖。这就像问为什么var i=3和int i=3I是一样的,但这种设计让代理更像是二等公民。就像在某些上下文中,
k
实际上是
k.Invoke
,但在其他情况下,
k
只是一个委托。我认为这是因为就CLR而言,委托实际上(几乎)是普通类。为了让他们成为C#中的一等公民,你必须求助于这样的东西。普通的类是一等公民,提供一些神奇的功能使他们成为二等公民(好吧,那是特定的功能)。等等,那篇文章让人困惑。新的委托只是一个包装器,在调用列表中只有一个项。否则所有东西都会被调用两次。但是我可以看到,多播委托使这成为一个复杂的场景。
Console.WriteLine(wrapped2.GetInvocationList().Length)打印1。违反了规范(或只是指定不正确)。@leppie:这取决于您是将“调用列表”简单地视为“调用委托时最终被调用的方法”还是实现细节(使用
GetInvocationList
等)。我认为,如果你仅仅依赖于规范中的内容,编译器的行为是一致的。如果它说:“新的委托实例只是在给定为E的委托上调用Invoke”,那就更清楚了。
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