为何传递dynamic参数调用扩展方法时触发CS1973错误?
为什么传入dynamic参数调用扩展方法会触发CS1973?
这个问题的核心在于扩展方法的本质和C#动态调度规则之间的冲突,咱们一步步拆解深层原因:
1. 扩展方法只是编译器的语法糖
首先要明确:CLR运行时根本不知道“扩展方法”是什么东西。扩展方法本质上是静态类里的静态方法,编译器只是给我们提供了一个“看起来像实例方法”的语法糖——当你写test.ExtensionMethod(d)时,编译器在编译期会把它转换成Program.ExtensionMethod(test, d)。
但这里有个关键前提:编译器必须能在编译期确定要调用哪个扩展方法。
2. 只要参数里有dynamic,就会触发动态调度
当你的调用中存在dynamic类型的参数时(这里是第二个参数d),C#编译器会放弃编译期的方法绑定,把整个调用交给运行时动态调度(DLR)。
但问题来了:运行时的动态调度系统只会在目标对象的实例方法里查找匹配的方法,它完全不知道编译器生成的扩展方法语法糖。所以当你用扩展方法语法调用且带dynamic参数时,运行时根本找不到这个“看起来像实例方法”的扩展方法,编译器提前预判到了这个矛盾,就抛出了CS1973错误。
3. 为什么其他调用方式能正常工作?
咱们逐个分析:
- 静态方法调用
Program.ExtensionMethod(test, d):这里你直接指定了要调用的静态方法,编译器不需要做扩展方法的语法糖转换,也不需要动态查找方法——它明确知道要调用哪个方法,只是方法内部的dynamic args会做动态处理,这完全没问题。 - 强转成
object传入test.ExtensionMethod((object)d):把dynamic强转成object后,第二个参数的编译期类型就变成了object,不再是dynamic。这时候编译器可以在编译期确定要调用的扩展方法(因为object可以隐式转换为dynamic参数),直接完成语法糖转换,不会触发动态调度。 - 传入匿名对象
test.ExtensionMethod(new { World = "world" }):匿名对象的编译期类型是编译器生成的具体类型,不是dynamic。同样,编译器能在编译期匹配到扩展方法的dynamic参数(任何类型都能隐式转成dynamic),直接完成绑定,不需要动态调度。
简单总结:扩展方法语法依赖编译器的静态绑定,而只要有dynamic参数参与,就会触发动态调度,这两者天生不兼容——因为运行时看不到扩展方法。
内容的提问来源于stack exchange,提问作者nvoigt
相关产品推荐
相关产品推荐

