问题描述
可以将扩展方法分配给与其在对象上的用法相匹配的委托,如下所示:
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
这给了
错误 CS1113:定义了扩展方法FunnyExtension.Double(int)"on value type 'int' 不能用于创建委托.
Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates.
他们为什么不能?
推荐答案
针对我的其他回答,Eric Smith 正确地指出:
In response to my other answer, Eric Smith correctly notes:
...因为它需要隐式装箱接收器类型参数...".如果您执行以下操作,无论如何都会发生这种情况: Func f = 5.ToString;这是完全合法的.
"... 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.
思考这个问题让我有了一个新的答案.试穿这个尺寸:
Thinking about this has led me to a new answer. Try this on for size:
结构上的普通实例"方法在 CIL 级别采用托管指针"(类型 &
)作为接收器参数.这是必要的,以便结构上的实例方法可以分配给结构的字段.请参阅 分区 II,第 13.3 节.
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.
类似地,类上的实例方法将对象引用"(类型 O
)作为接收器参数(不同之处在于这是指向托管堆的指针,需要跟踪GC).
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).
由于 CIL &
s 和 O
s 都可以(并且正在)由指针实现,所以对于委托实现来说,一切都是笨拙的.无论委托是捕获静态方法、类实例方法还是结构实例方法,它所需要做的就是将指向其 _target
的指针传递给函数的第一个参数.
Since both CIL &
s and O
s 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.
但我们正在讨论的场景破坏了这一点.将 int
作为第一个参数的静态扩展方法需要 int32
类型的 CIL 参数(参见第 III 部分,第 1.1.1 节).这就是事情出轨的地方.我认为没有任何理由可能让委托的实现意识到这种情况正在发生(对于例如,通过检查与捕获的 MethodInfo 相关联的元数据)并发出一个 thunk 将 _target
拆箱并将其作为第一个参数传递,但 这对于经典的委托不需要结构上的实例方法,因为它们无论如何都期望指针并且不会出现(根据我之前不正确答案中的示例判断)被实现.显然,所讨论的特定值类型将控制所需 thunk 的确切性质.
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.
除非我遗漏了一个更根本的实现障碍(例如,我可以想象它会给验证者带来问题),似乎可以提出一个合理的案例来扩展运行时以支持这种情况,但所有迹象表明这是运行时的限制,而不是 C# 编译器本身的限制.
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.
这篇关于在值类型上定义的扩展方法不能用于创建委托 - 为什么不呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!