我有以下接口是现有项目的一部分.我想用动态对象调用Store(..)函数.但我不想更改接口层次结构(如果可能的话).
public interface IActualInterface { void Store(object entity); } public interface IExtendedInterface : IActualInterface { //Interface items not important } public class Test : IExtendedInterface { public void Store(object entity) { Console.WriteLine("Storing: " + entity.ToString()); } }
和以下代码:
IExtendedInterface extendedInterfaceTest = new Test(); IActualInterface actualInterfaceTest = new Test(); Test directTest = new Test(); dynamic employee = new ExpandoObject(); employee.Name = "John Smith"; employee.Age = 33; employee.Phones = new ExpandoObject(); employee.Phones.Home = "0111 123123"; employee.Phones.Office = "027 321123"; employee.Tags = new List() { 123.4D, 99.54D }; try { extendedInterfaceTest .Store(employee); } catch (RuntimeBinderException rbEx) { Console.WriteLine(rbEx.Message); } //Casting as (object) works okay as it's not resolved at runtime extendedInterfaceTest.Store((object)employee); //this works because IActualInterface implements 'Store' actualInterfaceTest.Store(employee); //this also works okay (directTest : IProxyTest) directTest.Store(employee);
当我调用时extendedInterfaceTest.Store(employee)
,它会引发运行时绑定程序异常.为什么接口类型在相同的底层类型时有所不同?我可以把它IActualInterface
和Type
,但不IExtendedInterface
?
我知道在使用动态参数调用函数时,解析会在运行时发生,但为什么不同的行为呢?
您需要记住的是,动态分辨率基本上与静态分辨率执行相同的过程,但在运行时.DLR无法解决CLR无法解决的任何问题.
让我们来看看这个小程序,受你的启发,并且根本不使用动态:
namespace ConsoleApplication38 { public interface IActualInterface { void Store(object entity); } public interface IExtendedInterface : IActualInterface { } public class TestInterface : IExtendedInterface { public void Store(object entity) { } } public abstract class ActualClass { public abstract void Store(object entity); } public abstract class ExtendedClass : ActualClass { } public class TestClass : ExtendedClass { public override void Store(object entity) { } } class Program { static void TestInterfaces() { IActualInterface actualTest = new TestInterface(); IExtendedInterface extendedTest = new TestInterface(); TestInterface directTest = new TestInterface(); actualTest.Store(null); extendedTest.Store(null); directTest.Store(null); } static void TestClasses() { ActualClass actualTest = new TestClass(); ExtendedClass extendedTest = new TestClass(); TestClass directTest = new TestClass(); actualTest.Store(null); extendedTest.Store(null); directTest.Store(null); } static void Main(string[] args) { TestInterfaces(); TestClasses(); } } }
一切都很好.但编译器真正生成了什么?让我们看看使用ILdasm.
对于接口:
// actualTest.Store IL_0015: callvirt instance void ConsoleApplication38.IActualInterface::Store(object) // extendedTest.Store IL_001d: callvirt instance void ConsoleApplication38.IActualInterface::Store(object) // directTest.Store IL_0025: callvirt instance void ConsoleApplication38.TestInterface::Store(object)
我们在这里可以看到C#编译器总是为定义方法的接口或类生成调用.IActualInterface
有一个Store的方法槽,所以它用于actualTest.Store
.IExtendedInterface
没有,所以IActualInterface
用于通话.TestInterface
定义一个新方法Store,使用newslot
IL修饰符,有效地为该方法在vtable中分配一个新槽,因此它是直接使用的,因为directTest
它是类型TestInterface
.
对于班级:
// actualTest.Store IL_0015: callvirt instance void ConsoleApplication38.ActualClass::Store(object) // extendedTest.Store IL_001d: callvirt instance void ConsoleApplication38.ActualClass::Store(object) // directTest.Store IL_0025: callvirt instance void ConsoleApplication38.ActualClass::Store(object)
对于3种不同的类型,生成相同的调用,因为方法槽在ActualClass上定义.
现在让我们看看如果我们自己编写IL,使用我们想要的类型而不是让C#编译器为我们选择它.我修改了IL看起来像这样:
对于接口:
// actualTest.Store IL_0015: callvirt instance void ConsoleApplication38.IActualInterface::Store(object) // extendedTest.Store IL_001d: callvirt instance void ConsoleApplication38.IExtendedInterface::Store(object) // directTest.Store IL_0025: callvirt instance void ConsoleApplication38.TestInterface::Store(object)
对于课程:
// actualTest.Store IL_0015: callvirt instance void ConsoleApplication38.ActualClass::Store(object) // extendedTest.Store IL_001d: callvirt instance void ConsoleApplication38.ExtendedClass::Store(object) // directTest.Store IL_0025: callvirt instance void ConsoleApplication38.TestClass::Store(object)
该程序与ILasm编译良好.但是它无法在运行时传递peverify并崩溃,并出现以下错误:
未处理的异常:System.MissingMethodException:找不到方法:'Void ConsoleApplication38.IExtendedInterface.Store(System.Object)'.在ConsoleApplication38.Program.Main(String [] args)的ConsoleApplication38.Program.TestInterfaces()中
如果删除此无效调用,派生类调用可以正常工作而不会出现任何错误.CLR能够从派生类型调用中解析基本方法.但是,接口在运行时没有真正的表示,并且CLR无法从扩展接口解析方法调用.
理论上,C#编译器可以直接将调用发送到运行时中指定的正确类.如Eric Lippert的博客所示,它可以避免中产阶级调用的问题.但是,如所示,接口不可能实现.
让我们回到DLR.它解析方法的方式与CLR完全相同.我们已经看到IExtendedInterface.Store
CLR无法解决这个问题.DLR也不能!C#编译器将发出正确的调用这一事实完全隐藏了这一点,因此在使用时要小心,dynamic
除非您完全知道它在CLR中是如何工作的.