2010-06-18 3 views
33

У меня есть следующие интерфейсы, которые являются частью существующего проекта. Я хочу, чтобы можно было вызвать функцию 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

Нашли это, работая на Ворон, не так ли? –

+1

@Chris конечно сделал !! –

ответ

81

Что нужно помнить, так это то, что динамическое разрешение в основном выполняет тот же процесс, что и статическое разрешение, но во время выполнения. Все, что не может быть разрешено 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 имеет слот для магазина, поэтому он используется для actualTest.Store. IExtendedInterface нет, поэтому для звонка используется IActualInterface. TestInterface определяет новый метод Store, используя модификатор IL newslot, эффективно назначая новый слот в таблице 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# выбрать его для нас. Я изменил ПЯ выглядеть следующим образом:

Для интерфейсов:

// 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 и аварий во время выполнения со следующей ошибкой:

Unhandled Exception: System.MissingMethodException: Method not found: 'Void ConsoleApplication38.IExtendedInterface.Store(System.Object)'. at ConsoleApplication38.Program.TestInterfaces() at ConsoleApplication38.Program.Main(String[] args)

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

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

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

+0

Спасибо за подробный ответ! –

+0

+1 для отличного ответа с недостаточным количеством оборотов. :) –

Смежные вопросы