2010-06-18 24 views
33

Mevcut bir projenin parçası olan aşağıdaki arabirimlere sahibim. Mağaza (..) işlevini dinamik nesnelerle çağırmayı mümkün kılmak istiyorum. Ancak, Arabirim hiyerarşisini değiştirmek istemiyorum (eğer mümkünse). Dinamik türleri, yöntem parametreleri olarak kullanırken garip davranış

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()); 
    }  
} 

ve aşağıdaki kodu: Ben extendedInterfaceTest.Store(employee) çağırdığınızda

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); 

, bir çalışma zamanı bağlayıcı durum oluşturur. Arabirim türü neden aynı altta yatan türden bir fark yaratıyor? IActualInterface ve Type numaralı telefonlardan arayabilirim, ancak IExtendedInterface?

Dinamik bir parametreyle bir işlevi çağırırken, çözümün çalışma zamanında gerçekleştiğini, ancak neden farklı davranışların olduğunu anlıyorum.

+1

Bu çalışma Raven'de buldunuz değil mi? –

+1

@Chris emin yaptım! –

cevap

81

Hatırlamanız gereken şey, dinamik çözümün temelde statik çözünürlükle aynı işlemi gerçekleştirmesidir, ancak çalışma zamanında. CLR tarafından çözülemeyen herhangi bir şey DLR tarafından çözülmeyecektir.

seninkinden esinlenerek bu küçük program alalım ve bu hiç de dinamik kullanmaz:

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(); 
     } 
    } 
} 

Her şey iyi derler. Ama derleyici gerçekten ne üretti? ILdasm kullanarak görelim. arabirimler için

:

// 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) 

Biz C# derleyicisi daima yöntemi tanımlanır arayüzü veya sınıf için çağrılarını oluşturduğu burada görebilirsiniz. IActualInterface'un Store için bir yöntem yuvası vardır, bu nedenle actualTest.Store için kullanılır. IExtendedInterface, arama için IActualInterface kullanmıyor. TestInterface, directTest türünde TestInterface türünde olduğundan, doğrudan bu yöntem için vtable'da yeni bir yuva atayarak, newslot IL değiştiricisini kullanan yeni bir yöntem deposunu tanımlar. sınıfları için

: yöntem yuvası ActualClass tanımlı olduğu için, 3 farklı tipleri için

// 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) 

, aynı çağrı oluşturulur.

Şimdi IL'leri kendimiz yazıp, C# derleyicisini bizim için seçmesine izin vermek yerine istediğimiz türü kullanarak yazdığımızı görelim. Böyle görünmek IL değiştirdik:

arayüzleri için:

// 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) 

sınıflar için:

// 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) 

programı ILASM ile iyi derler. Ancak aşağıdaki hata ile zamanında peverify ve çöker geçemeyen: Bu geçersiz çağrıyı kaldırırsanız

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

, türetilmiş sınıfları çağrılar herhangi bir hata olmadan iyi çalışır. CLR, türetilmiş tip çağrısından temel yöntemi çözebilir.Ancak, arabirimlerin çalışma zamanında gerçek bir temsili yoktur ve CLR, yöntem çağrısını genişletilmiş arabirimden çözemez.

Teoride, C# derleyicisi çağrıyı doğrudan çalışma zamanında belirtilen doğru sınıfa verebilir. Eric Lippert's blog'da görüldüğü gibi orta sınıf çağrıları ile ilgili sorunlardan kaçınacaktır. Ancak gösterildiği gibi, bu arayüzler için mümkün değildir.

DLR'ye dönelim. Yöntemi, CLR ile tam olarak aynı şekilde giderir. IExtendedInterface.Store'un CLR tarafından çözülemediğini gördük. DLR de yapamaz! Bu, C# derleyicisinin doğru çağrıyı yayınlayacağı gerçeği tarafından tamamen gizlidir, bu nedenle CLR'de nasıl çalıştığını tam olarak bilmiyorsanız, dynamic kullanırken her zaman dikkatli olun.

+0

Derinlemesine cevap için teşekkürler! Yetersiz yükseltmelerle mükemmel bir yanıt için –

+0

+1. :) –