What you need to remember is that dynamic resolution basically does the same process as static resolution, but at runtime. Anything that couldn't be resolved by the CLR won't be resolved by the DLR.
Let's take this small program, inspired by yours, and that doesn't use dynamic at all:
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();
}
}
}
Everything compiles fine. But what did the compiler really generate? Let's see using ILdasm.
For the interfaces:
// 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)
We can see here that the C# compiler always generates calls for the interface or class where the method is defined. IActualInterface
has a method slot for Store so it's used for actualTest.Store
. IExtendedInterface
doesn't, so IActualInterface
is used for the call. TestInterface
defines a new method Store, using the newslot
IL modifier, effectively assigning a new slot in the vtable for that method, so it's directly used since directTest
is of type TestInterface
.
For the classes:
// 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)
For the 3 different types, the same call is generated because the method slot is defined on ActualClass.
Let's now see what we get if we write the IL ourselves, using the type we want rather than letting the C# compiler choosing it for us. I've modified the IL to look like this:
For interfaces:
// 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)
For classes:
// 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)
The program compiles fine with ILasm. However it fails to pass peverify and crashes at runtime with the following error:
Unhandled Exception:
System.MissingMethodException: Method
not found: 'Void
ConsoleApplication38.IExtendedInterface.Store(System.Object)'.
at
ConsoleApplication38.Program.TestInterfaces()
at
ConsoleApplication38.Program.Main(String[]
args)
If you remove this invalid call, the derived classes calls work fine without any error. The CLR is able to resolve the base method from the derived type call. However interfaces have no true representation in runtime, and the CLR isn't able to resolve the method call from the extended interface.
In theory, the C# compiler could emit the call directly to the correct class specified in the runtime. It would avoid problems about middle classes calls as seen on Eric Lippert's blog. However as demonstrated, this is not possible for interfaces.
Let's get back to the DLR. It resolves the method exactly the same way as the CLR. We've seen that IExtendedInterface.Store
couldn't be resolved by the CLR. The DLR cannot either! This is totally hidden by the fact that the C# compiler will emit the right call, so always be careful when using dynamic
unless you perfectly know how it works in the CLR.