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