使用dynamictypes作为方法参数时出现奇怪的行为

我有以下接口是现有项目的一部分。 我想使用dynamic对象调用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<dynamic>() { 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) ,它引发了一个运行时绑定exception。 为什么接口types在相同的基础types时有所作为? 我可以调用IActualInterfaceType ,但不是IExtendedInterface

我明白,当用dynamic参数调用一个函数时,parsing发生在运行时,但是为什么不同的行为呢?

你需要记住的是dynamic分辨率基本上和静态分辨率一样,但是在运行时。 任何CLR无法解决的问题都不会由DLR解决。

让我们来看看这个受你的启发的小程序,它根本不使用dynamic:

 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.StoreIExtendedInterface不,所以IActualInterface用于调用。 TestInterface定义了一个新的方法Store,使用newslot IL修饰符,为该方法在vtable中有效地分配一个新槽,所以直接使用它,因为directTestTestInterfacetypes的。

对于课程:

 // 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种不同的types,由于在ActualClass上定义了方法槽,所以会生成相同的调用。

现在让我们看看如果我们自己编写IL,使用我们想要的types而不是让C#编译器为我们select它,我们会得到什么。 我修改了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和崩溃,并出现以下错误:

未处理的exception:System.MissingMethodException:找不到方法:'无效ConsoleApplication38.IExtendedInterface.Store(System.Object)'。 ConsoleApplication38.Program.Main(String [] args)上的ConsoleApplication38.Program.TestInterfaces()

如果你删除这个无效的调用,派生类调用工作正常,没有任何错误。 CLR能够从派生types调用中parsing基本方法。 但是,接口在运行时没有真实的表示,CLR也无法parsing来自扩展接口的方法调用。

理论上,C#编译器可以直接将调用发送到运行时指定的正确类。 这样可以避免在Eric Lippert的博客上看到的中产阶级电话问题。 然而如所certificate的,这对于接口是不可能的。

我们回到DLR。 它解决了与CLR完全相同的方法。 我们已经看到, IExtendedInterface.Store无法由CLR解决。 DLR也不能! 这完全是隐藏的,因为C#编译器会发出正确的调用,所以在使用dynamic时候一定要小心,除非你完全知道它在CLR中是如何工作的。