Ответ 1
Что нужно помнить, так это то, что динамическое разрешение в основном выполняет тот же процесс, что и статическое разрешение, но во время выполнения. Все, что не может быть разрешено с помощью 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)
Мы видим здесь, что компилятор С# всегда генерирует вызовы для интерфейса или класса, где определяется метод. 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)
Для трех разных типов один и тот же вызов генерируется, потому что слот метода определен в ActualClass.
Теперь посмотрим, что получим, если мы сами напишем IL, используя нужный тип, а не позволяем компилятору С# выбрать его для нас. Я изменил 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. Тем не менее, он не может пройти проверку и сбой во время выполнения со следующей ошибкой:
Необработанное исключение: System.MissingMethodException: метод не найден: "Пустота ConsoleApplication38.IExtendedInterface.Store(System.Object). в ConsoleApplication38.Program.TestInterfaces() в ConsoleApplication38.Program.Main(String [] арг)
Если вы удалите этот недействительный вызов, вызовы производных классов отлично работают без каких-либо ошибок. CLR может разрешить базовый метод из вызова производного типа. Однако интерфейсы не имеют истинного представления во время выполнения, и среда CLR не может разрешить вызов метода из расширенного интерфейса.
В теории, компилятор С# мог бы выдать вызов непосредственно правильному классу, указанному во время выполнения. Это позволит избежать проблем с вызовами средних классов, как показано на блоге Эрика Липперта. Однако, как показано, это невозможно для интерфейсов.
Вернемся к DLR. Он решает метод точно так же, как и CLR. Мы видели, что IExtendedInterface.Store
не может быть разрешен CLR. DLR тоже не может! Это полностью скрывается из-за того, что компилятор С# будет выдавать правильный вызов, поэтому всегда будьте осторожны при использовании dynamic
, если вы не знаете, как это работает в среде CLR.