Lo que debe recordar es que la resolución dinámica básicamente realiza el mismo proceso que la resolución estática, pero en tiempo de ejecución. Cualquier cosa que no pueda ser resuelta por el CLR no será resuelta por el DLR.
Tomemos este pequeño programa, inspirado en la suya, y que no utiliza en absoluto dinámico:
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();
}
}
}
Todo compila bien. Pero, ¿qué generó realmente el compilador? Veamos el uso de ILdasm.
Para las 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)
Podemos ver aquí que el compilador de C# siempre genera peticiones de la interfaz o clase en la que se define el método. IActualInterface
tiene una ranura de método para Tienda por lo que se usa para actualTest.Store
. IExtendedInterface
no, por lo que IActualInterface
se usa para la llamada. TestInterface
define un nuevo método Almacenar, utilizando el modificador newslot
IL, asignando efectivamente un nuevo intervalo en el vtable para ese método, por lo que se usa directamente ya que directTest
es del tipo TestInterface
.
Para las clases:
// 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)
Para los 3 tipos diferentes, se genera la misma llamada porque la ranura método se define en ActualClass.
Veamos ahora qué obtenemos si escribimos la IL nosotros mismos, usando el tipo que queremos en lugar de dejar que el compilador de C# lo elija por nosotros. He modificado la IL a tener este aspecto:
Para las 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)
Para las clases:
// 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)
El programa compila bien con ILASM. Sin embargo, no logra pasar a PEverify y se estrella en tiempo de ejecución con el error siguiente:
Unhandled Exception: System.MissingMethodException: Method not found: 'Void ConsoleApplication38.IExtendedInterface.Store(System.Object)'. at ConsoleApplication38.Program.TestInterfaces() at ConsoleApplication38.Program.Main(String[] args)
Si se quita esta llamada no válida, las clases de llamadas derivadas funcionan bien sin ningún error. El CLR puede resolver el método base desde la llamada de tipo derivada.Sin embargo, las interfaces no tienen representación verdadera en el tiempo de ejecución, y el CLR no puede resolver la llamada al método desde la interfaz extendida.
En teoría, el compilador de C# podría emitir la llamada directamente a la clase correcta especificada en el tiempo de ejecución. Evitaría problemas sobre llamadas de clase media como se ve en Eric Lippert's blog. Sin embargo, como se demostró, esto no es posible para las interfaces.
Volvamos al DLR. Resuelve el método exactamente de la misma manera que el CLR. Hemos visto que el CLR no pudo resolver IExtendedInterface.Store
. ¡El DLR tampoco! Esto está totalmente oculto por el hecho de que el compilador de C# emitirá la llamada correcta, por lo que siempre tenga cuidado al usar dynamic
a menos que sepa perfectamente cómo funciona en el CLR.
Encontré esto trabajando en Raven ¿no? –
@Chris seguro !! –