Странное поведение при использовании динамических типов в качестве параметров метода - PullRequest
37 голосов
/ 18 июня 2010

У меня есть следующие интерфейсы, которые являются частью существующего проекта. Я хотел бы сделать возможным вызывать функцию Store (..) с динамическими объектами. Но я не хочу менять иерархию интерфейса (если это вообще возможно).

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?

Я понимаю, что при вызове функции с динамическим параметром разрешение происходит во время выполнения, но почему существует другое поведение?

1 Ответ

86 голосов
/ 18 июня 2010

Необходимо помнить, что динамическое разрешение в основном выполняет те же действия, что и статическое, но во время выполнения.Все, что не может быть разрешено 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: Метод не найден: 'Void ConsoleApplication38.IExtendedInterface.Store (System.Object)'.в ConsoleApplication38.Program.TestInterfaces () в ConsoleApplication38.Program.Main (String [] args)

Если вы удалите этот недопустимый вызов, вызовы производных классов будут работать без ошибок.CLR может разрешить базовый метод из вызова производного типа.Однако интерфейсы не имеют истинного представления во время выполнения, и CLR не может разрешить вызов метода из расширенного интерфейса.

Теоретически, компилятор C # может отправлять вызов непосредственно в правильный класс, указанный вво время выполнения.Это позволило бы избежать проблем с вызовами среднего класса, как видно из в блоге Эрика Липперта .Однако, как показано, это невозможно для интерфейсов.

Вернемся к DLR.Он разрешает метод точно так же, как и CLR.Мы видели, что CLR не может разрешить IExtendedInterface.Store.DLR тоже не может!Это полностью скрыто тем фактом, что компилятор C # отправит правильный вызов, поэтому всегда будьте осторожны при использовании dynamic, если вы не знаете, как он работает в CLR.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...