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

时间:2022-03-29 04:14:54

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?

他们为什么不能?

2 个解决方案

#1


18  

In response to my other answer, Eric Smith correctly notes:

为了回应我的另一个回答,埃里克·史密斯正确地指出:

"... 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; Which is perfectly legal.

“…因为它需要隐式地装箱接收类型参数……这就是发生的情况,如果你这样做,Func f = 5; tostring;这是完全合法的。

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. This is necessary so that instance methods on structs can assign to fields of the struct. See Partition II, Section 13.3.

结构上的普通“实例”方法在CIL级别上使用“托管指针”(类型&)作为接收参数。这是必要的,这样结构上的实例方法就可以分配给结构的字段。参见分区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 Os can be (and are) implemented by pointers, everything is hunky-dory for the delegate implementation. 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.

由于CIL &s和Os都可以(并且是)通过指针实现的,因此委托实现的一切都是完美的。不管委托是否捕获静态方法、类实例方法或结构实例方法,它所需要做的就是将指针传递给函数的第一个参数。

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). 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. Obviously the specific value type in question would control the exact nature of the required thunk.

但我们正在讨论的情形破坏了这一点。将int作为第一个参数的静态扩展方法需要int32类型的CIL参数(参见分区III 1.1.1节)。这就是事情偏离轨道的地方。我看不出任何原因它不可能实现的代表们意识到这是发生相关的元数据(例如,通过检查MethodInfo被俘虏)和发出的一声从箱子中取出_target并传递,作为第一个参数,但这并不代表古典所需结构上的实例方法,因为他们期望一个指针,不出现(从我之前错误的答案)中的示例实现。显然,所讨论的特定值类型将控制所需的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#编译器本身。

#2


2  

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. See my other answer for a different take on the matter.

编辑2我不再相信这个答案了,但是我把它放在这里,这样线程仍然有意义,这样人们就能看到为什么它不对。看看我的另一个答案,对这个问题有不同的看法。

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.

因为它需要隐式装箱值类型receiver参数(因为系统中的_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." at the call to CreateDelegate. I'm not sure why, and because the relevant method is an internalcall method, Reflector isn't much help. The documentation for CreateDelegate also wasn't much help. I'm sure it has something to do with boxing the receiver, maybe someone with knowledge of the Rotor source could help explain why?

并且得到了一个ArgumentException:“错误绑定到目标方法”,在调用CreateDelegate时。我不知道为什么,因为相关的方法是内部调用方法,所以Reflector没有多大帮助。CreateDelegate的文档也没有多大帮助。我相信这和接收器的封装有关,也许有些了解转子源的人可以解释为什么?

#1


18  

In response to my other answer, Eric Smith correctly notes:

为了回应我的另一个回答,埃里克·史密斯正确地指出:

"... 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; Which is perfectly legal.

“…因为它需要隐式地装箱接收类型参数……这就是发生的情况,如果你这样做,Func f = 5; tostring;这是完全合法的。

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. This is necessary so that instance methods on structs can assign to fields of the struct. See Partition II, Section 13.3.

结构上的普通“实例”方法在CIL级别上使用“托管指针”(类型&)作为接收参数。这是必要的,这样结构上的实例方法就可以分配给结构的字段。参见分区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 Os can be (and are) implemented by pointers, everything is hunky-dory for the delegate implementation. 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.

由于CIL &s和Os都可以(并且是)通过指针实现的,因此委托实现的一切都是完美的。不管委托是否捕获静态方法、类实例方法或结构实例方法,它所需要做的就是将指针传递给函数的第一个参数。

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). 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. Obviously the specific value type in question would control the exact nature of the required thunk.

但我们正在讨论的情形破坏了这一点。将int作为第一个参数的静态扩展方法需要int32类型的CIL参数(参见分区III 1.1.1节)。这就是事情偏离轨道的地方。我看不出任何原因它不可能实现的代表们意识到这是发生相关的元数据(例如,通过检查MethodInfo被俘虏)和发出的一声从箱子中取出_target并传递,作为第一个参数,但这并不代表古典所需结构上的实例方法,因为他们期望一个指针,不出现(从我之前错误的答案)中的示例实现。显然,所讨论的特定值类型将控制所需的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#编译器本身。

#2


2  

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. See my other answer for a different take on the matter.

编辑2我不再相信这个答案了,但是我把它放在这里,这样线程仍然有意义,这样人们就能看到为什么它不对。看看我的另一个答案,对这个问题有不同的看法。

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.

因为它需要隐式装箱值类型receiver参数(因为系统中的_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." at the call to CreateDelegate. I'm not sure why, and because the relevant method is an internalcall method, Reflector isn't much help. The documentation for CreateDelegate also wasn't much help. I'm sure it has something to do with boxing the receiver, maybe someone with knowledge of the Rotor source could help explain why?

并且得到了一个ArgumentException:“错误绑定到目标方法”,在调用CreateDelegate时。我不知道为什么,因为相关的方法是内部调用方法,所以Reflector没有多大帮助。CreateDelegate的文档也没有多大帮助。我相信这和接收器的封装有关,也许有些了解转子源的人可以解释为什么?