C#继承类型中使用dynamic调用方法的异常行为及解决方案咨询
问题原因
这个问题的核心在于C#动态绑定的方法解析规则,以及编译时上下文和运行时类型的交互逻辑:
当你在Program类的Start方法中用dynamic类型调用DoSomething(dyn)时,动态绑定器的执行逻辑是这样的:
- 首先,它会基于调用点的编译时上下文(也就是
Program类)收集可见的DoSomething重载——这里只有基类定义的int和string版本。 - 运行时,它会尝试用
dyn的实际类型(比如long)去匹配这些收集到的重载。但基类里没有long版本的方法,绑定器只能尝试隐式类型转换,而long转int是需要显式声明的转换,无法自动完成,于是就抛出了RuntimeBinderException,甚至错误地认为DoSomething(int)是“最佳匹配”但参数不兼容。
而int和string版本的override能正常工作,是因为基类本身就有这两个重载,动态绑定器能找到它们,然后通过多态性调用子类的重写版本。但子类新增的long版本方法,在基类的编译时上下文中是不可见的,绑定器根本不会去子类里查找这个“额外”的重载。
解决方案
针对这个问题,有几种优雅的解决方式,推荐优先使用第一种:
1. 在基类中添加long版本的虚拟方法
这是最符合面向对象设计的方案:在基类Program中定义long类型的DoSomething虚拟方法,这样子类可以重写它,动态绑定器在基类上下文中就能看到这个重载,运行时会自动调用子类的实现。
修改基类代码:
public class Program { // ... 其他代码不变 ... protected virtual void DoSomething(long i) { // 基类默认实现,也可以抛出异常提示未实现 Console.WriteLine("Base Long: " + i); } }
然后子类重写该方法:
public class CoolProgram : Program { // ... 其他重写方法不变 ... protected override void DoSomething(long i) { Console.WriteLine("Long:" + i); } }
这样修改后,dynamic调用会正确匹配long类型,调用子类的重写方法。
2. 将调用上下文移到子类中
如果不想修改基类,可以把Start方法移到子类CoolProgram中(或者在子类中重新定义Start),这样调用点的编译时上下文变成了子类,动态绑定器就能看到子类新增的long版本方法。
示例:
public class CoolProgram : Program { // ... 其他重写方法不变 ... protected virtual void DoSomething(long i) { Console.WriteLine("Long:" + i); } // 重新定义Start方法,注意基类的Start需要改为protected/public权限 protected new void Start() { var arr = new object[] { 1, 1L, "Hello World" }; foreach (var dyn in arr.Cast<dynamic>()) { DoSomething(dyn); } Console.ReadKey(); } }
3. 强制转换运行时类型(不推荐)
如果你确定运行时对象是CoolProgram类型,可以在基类的Start方法中把this强制转换成CoolProgram,再调用方法:
foreach (var dyn in arr.Cast<dynamic>()) { ((CoolProgram)this).DoSomething(dyn); }
但这种方法灵活性很差,如果后续有其他继承Program的子类,就无法适配了。
内容的提问来源于stack exchange,提问作者dennis
相关产品推荐
相关产品推荐

