简体   繁体   English

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

[英]Extension methods defined on value types cannot be used to create delegates - Why not?

Extension methods can be assigned to delegates that match their usage on an object, like this: 可以将扩展方法分配给与对象的用法匹配的委托,如下所示:

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"

If the type they're extending is a value type, it won't work: 如果他们扩展的类型是值类型,它将不起作用:

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

This gives 这给了

Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates. 错误CS1113:在值类型“int”上定义的扩展方法'FunnyExtension.Double(int)'不能用于创建委托。

Why can't they? 他们为什么不能?

In response to my other answer, Eric Smith correctly notes: 在回答我的另一个答案时,Eric Smith正确地指出:

"... because it would require implicitly boxing the receiver type parameter ...". “...因为它需要隐式装箱接收器类型参数......”。 Which is what happens anyway, if you do something like this: Func f = 5.ToString; 无论如何会发生什么,如果你做这样的事情:Func f = 5.ToString; Which is perfectly legal. 这是完全合法的。

Thinking about this has led me to a new answer. 考虑到这一点,我得到了一个新的答案。 Try this on for size: 试试这个尺码:

Ordinary "instance" methods on structs take, at the CIL level, a "managed pointer" (type & ) as a receiver parameter. 结构上的普通“实例”方法在CIL级别采用“托管指针”(type & )作为接收器参数。 This is necessary so that instance methods on structs can assign to fields of the struct. 这是必要的,以便结构上的实例方法可以分配给结构的字段。 See Partition II, Section 13.3 . 第II部分,第13.3节

Similarly, instance methods on classes take an "object reference" (type O ) as a receiver parameter (the difference being that this is a pointer to the managed heap, and needs to be tracked for GC). 类似地,类上的实例方法将“对象引用”(类型O )作为接收器参数(不同之处在于它是指向托管堆的指针,需要为GC跟踪)。

Since both CIL & s and O s can be (and are) implemented by pointers, everything is hunky-dory for the delegate implementation. 由于两个CIL & S和O可以均为(并)通过指针来实现,一切都是没说的委托执行。 Regardless of whether a delegate captures a static method, a class instance method, or a struct instance method, all it needs to do is pass the pointer to its _target to the first argument of the function. 无论委托是否捕获静态方法,类实例方法或结构实例方法,它所需要做的就是将指针传递给它的_target到函数的第一个参数。

But the scenario we are discussing ruins that. 但我们正在讨论的情景破坏了这一点。 A static extension method taking an int as a first argument requires a CIL argument of type int32 (see Partition III, section 1.1.1). int作为第一个参数的静态扩展方法需要类型为int32的CIL参数(参见第III部分,第1.1.1节)。 Here is where things go off the rails. 这是事情发生的地方。 I don't see any reason why it wouldn't be possible for the implementation of delegates to realize that this was happening (for example, by inspecting the metadata associated with the MethodInfo being captured) and emit a thunk that would unbox the _target and pass that as the first argument, but this isn't needed for delegates to classical instance methods on structs, since they expect a pointer anyway and doesn't appear (judging by the example in my earlier incorrect answer) to be implemented. 我没有看到任何理由为什么代理的实现不可能意识到这种情况正在发生(例如,通过检查与正在捕获的MethodInfo相关联的元数据)并发出一个将取消打包_target和将其作为第一个参数传递,但对于结构体上的经典实例方法的委托不需要这样,因为它们无论如何都期望指针并且不会出现(通过我之前的错误答案中的示例来判断)。 Obviously the specific value type in question would control the exact nature of the required thunk. 显然,所讨论的特定值类型将控制所需thunk的确切性质。

Unless I am missing a more fundamental obstacle to implementation (I could imagine that it would pose problems for the verifier, for example), it seems like a reasonable case could be made for extending the runtime to support this case, but all the signs are pointing towards this being a limitation of the runtime and not of the C# compiler per se. 除非我错过了一个更基本的实现障碍(例如,我可以想象它会给验证器带来问题),似乎可以通过扩展运行时来支持这种情况,但所有的迹象都是合理的。指向这是运行时的限制,而不是C#编译器本身的限制。

EDIT 2 I don't believe this answer anymore, but I left it here so the thread would still make sense and so that people would see why it isn't right. 编辑2我不再相信这个答案,但我把它留在这里所以线程仍然有意义,所以人们会明白为什么它不对。 See my other answer for a different take on the matter. 请参阅我的其他答案,了解对此事的不同看法。

Original 原版的

Because it would require implicitly boxing the value type receiver parameter (because the _target field in the System.Delegate type which holds the the receiver parameter is of type System.Object), which could lead to some strange aliasing behavior if you weren't expecting it. 因为它需要隐式装箱值类型接收器参数(因为System.Delegate类型中包含接收器参数的_target字段是System.Object类型),如果你没有期待,这可能会导致一些奇怪的别名行为它。

EDIT 编辑

There is something else going on here. 还有其他事情发生在这里。 I ran this sample program: 我运行了这个示例程序:

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();
    }
}

and got an ArgumentException: "Error binding to target method." 并得到一个ArgumentException:“绑定到目标方法的错误。” at the call to CreateDelegate. 在调用CreateDelegate时。 I'm not sure why, and because the relevant method is an internalcall method, Reflector isn't much help. 我不确定为什么,因为相关方法是一个internalcall调用方法,Reflector没有太大帮助。 The documentation for CreateDelegate also wasn't much help. CreateDelegate文档也没什么帮助。 I'm sure it has something to do with boxing the receiver, maybe someone with knowledge of the Rotor source could help explain why? 我确定它与装箱接收器有关,也许知道Rotor源的人可以帮忙解释原因吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM