您需要记住的是,动态解析基本上与静态解析执行相同的过程,但在运行时。 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 定义了一个新方法 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:方法
未找到:'无效
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 中的工作原理。