我有以下接口是现有项目的一部分。我想用动态对象调用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)
时,它会引发运行时绑定程序异常。为什么接口类型在相同的底层类型时有所不同?我可以在IActualInterface
和Type
上调用它,但不能在IExtendedInterface
上调用它?
据我所知,在使用动态参数调用函数时,解析会在运行时发生,但为什么会出现不同的行为?
答案 0 :(得分:86)
您需要记住的是,动态分辨率基本上与静态分辨率执行相同的过程,但在运行时。任何无法通过CLR解决的问题都不会被DLR解决。
让我们从你的灵感来看这个小程序,并且根本不使用动态:
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
使用newslot
IL修饰符定义了一个新方法Store,有效地为该方法在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:方法 没找到:'空虚 ConsoleApplication38.IExtendedInterface.Store(System.Object的)”。 在 ConsoleApplication38.Program.TestInterfaces() 在 ConsoleApplication38.Program.Main(字符串[] 参数)
如果删除此无效调用,派生类调用可以正常工作而不会出现任何错误。 CLR能够从派生类型调用中解析基本方法。但是,接口在运行时没有真正的表示,并且CLR无法从扩展接口解析方法调用。
理论上,C#编译器可以直接将调用发送到运行时中指定的正确类。它可以避免在Eric Lippert's blog上看到的中产阶级调用问题。但是,如所示,接口无法实现这一点。
让我们回到DLR。它解析方法的方式与CLR完全相同。我们已经看到CLR无法解决IExtendedInterface.Store
。 DLR也不能! C#编译器会发出正确的调用这一事实完全隐藏了这一点,所以在使用dynamic
时要小心,除非你完全知道它在CLR中是如何工作的。