在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?

Posted

技术标签:

【中文标题】在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?【英文标题】:Extension methods defined on value types cannot be used to create delegates - Why not?在值类型上定义的扩展方法不能用于创建委托 - 为什么不呢? 【发布时间】:2010-11-04 04:49:36 【问题描述】:

可以将扩展方法分配给与其在对象上的用法相匹配的委托,如下所示:

static class FunnyExtension 
    public static string Double(this string str)  return str + str; 
    public static int Double(this int num)  return num + num; 



Func<string> aaMaker = "a".Double;
Func<string, string> doubler = FunnyExtension.Double;

Console.WriteLine(aaMaker());       //Prints "aa"
Console.WriteLine(doubler("b"));    //Prints "bb"

如果他们扩展的类型是值类型,它就不起作用:

Func<int> eightMaker = 4.Double;    //Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates
Func<int, int> intDoubler = FunnyExtension.Double;  //Works

这给了

错误 CS1113:定义了扩展方法“FunnyExtension.Double(int)” on value type 'int' 不能用于创建委托。

他们为什么不能?

【问题讨论】:

确定不是CS1113? 【参考方案1】:

在回应我的其他回答时,Eric Smith 正确地指出:

“...因为它需要隐式装箱接收器类型参数...”。如果您执行以下操作,无论如何都会发生这种情况: Func f = 5.ToString;这是完全合法的。

思考这个问题让我有了一个新的答案。试穿这个尺寸:

结构上的普通“实例”方法在 CIL 级别采用“托管指针”(类型 &amp;)作为接收器参数。这是必要的,以便结构上的实例方法可以分配给结构的字段。见Partition II, Section 13.3。

类似地,类上的实例方法将“对象引用”(类型O)作为接收器参数(不同之处在于这是指向托管堆的指针,需要为 GC 进行跟踪)。

由于 CIL &amp;s 和 Os 都可以(并且正在)通过指针实现,所以对于委托实现来说,一切都很简单。无论委托是捕获静态方法、类实例方法还是结构实例方法,它所需要做的就是将指向其_target 的指针传递给函数的第一个参数。

但我们正在讨论的场景破坏了这一点。将int 作为第一个参数的静态扩展方法需要int32 类型的CIL 参数(参见Partition III,第1.1.1 节)。 这就是事情出轨的地方。我认为没有任何理由可能让委托的实现意识到这种情况正在发生(对于例如,通过检查与捕获的 MethodInfo 相关联的元数据)并发出一个 thunk 将拆箱 _target 并将其作为第一个参数传递,但是 这对于结构上的经典实例方法的委托来说不是必需的,因为他们无论如何都希望有一个指针并且不会出现(根据我之前不正确答案中的示例判断)来实现。显然,所讨论的特定值类型将控制所需 thunk 的确切性质。

除非我遗漏了一个更根本的实施障碍(例如,我可以想象它会给验证者带来问题),似乎可以提出一个合理的案例来扩展运行时以支持这种情况,但所有迹象表明这是运行时的限制,而不是 C# 编译器本身的限制。

【讨论】:

道格,您的分析非常出色。正如我们通过电子邮件讨论的那样,我的同事 Sreekar 在他的博客 blogs.msdn.com/sreekarc/archive/2009/06/25/… 中做了一些笔记,我已经在我的博客中记录了这个问题是如何通过“反射柯里化”出现的。 blogs.msdn.com/ericlippert/archive/2009/06/25/mmm-curry.aspx 非常感谢 Eric 和 Sreekar 花时间深入研究并为大家解释! 另见博文Open Delegates vs. Closed Delegates,后来由提问者(S. Laks)撰写。【参考方案2】:

编辑 2 我不再相信这个答案,但我把它留在这里,所以这个话题仍然有意义,这样人们就会明白为什么它是不正确的。请参阅我的其他答案以了解对此问题的不同看法。

原创

因为它需要对值类型接收器参数进行隐式装箱(因为保存接收器参数的 System.Delegate 类型中的 _target 字段是 System.Object 类型),如果你没有,这可能会导致一些奇怪的别名行为没想到。

编辑

这里还有其他事情发生。我运行了这个示例程序:

class Program

    public static int Combine(int a, int b)
    
        return a + b;
    

    static void Main(string[] args)
    
        var combineMethod = typeof(Program).GetMethod("Combine");
        var add4 = Delegate.CreateDelegate(
                              typeof(Converter<int, int>),
                              4,
                              combineMethod) as Converter<int, int>;

        for (int i = 0; i < 10; i++)
        
            Console.WriteLine(add4(i));
        
        Console.ReadLine();
    

并得到一个 ArgumentException:“错误绑定到目标方法。”在调用 CreateDelegate 时。我不知道为什么,因为相关方法是internalcall 方法,Reflector 并没有太大帮助。 documentation for CreateDelegate 也没有多大帮助。我确定这与接收器的拳击有关,也许了解转子来源的人可以帮助解释原因?

【讨论】:

什么奇怪的别名行为? 它会导致哪些你意想不到的混叠行为?由于扩展方法已经将值类型作为常规参数,因此没有理由假设它不会复制。 如果您两次调用委托,它将位于结构的同一个盒装副本上。我不确定这是否符合意外情况。 这会有什么不同?该函数仍将在副本上运行(它不是 ref 参数) 我想说您正在尝试使用接收类型 Convert 但使用属于 Program 类型的方法创建一个委托。这就是为什么你在你的例子中得到异常

以上是关于在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?的主要内容,如果未能解决你的问题,请参考以下文章

委托 详细

C# 委托

委托基应用及系统定义的委托

C#新特性

委托(C# 编程指南)

c#中的委托01