在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?
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 级别采用“托管指针”(类型 &
)作为接收器参数。这是必要的,以便结构上的实例方法可以分配给结构的字段。见Partition II, Section 13.3。
类似地,类上的实例方法将“对象引用”(类型O
)作为接收器参数(不同之处在于这是指向托管堆的指针,需要为 GC 进行跟踪)。
由于 CIL &
s 和 O
s 都可以(并且正在)通过指针实现,所以对于委托实现来说,一切都很简单。无论委托是捕获静态方法、类实例方法还是结构实例方法,它所需要做的就是将指向其_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以上是关于在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?的主要内容,如果未能解决你的问题,请参考以下文章