2016-07-22 23 views
14

Dlaczego jawne wywołania interfejsu C# w ramach metody ogólnej, która ma ograniczenie typu interfejsu, zawsze wywołują implementację podstawową?Dlaczego jawne wywołania interfejsu dla generycznych zawsze wywołują implementację podstawową?

Rozważmy na przykład następujący kod:

public interface IBase 
{ 
    string Method(); 
} 

public interface IDerived : IBase 
{ 
    new string Method(); 
} 

public class Foo : IDerived 
{ 
    string IBase.Method() 
    { 
     return "IBase.Method"; 
    } 

    string IDerived.Method() 
    { 
     return "IDerived.Method"; 
    } 
} 

static class Program 
{ 
    static void Main() 
    { 
     IDerived foo = new Foo(); 
     Console.WriteLine(foo.Method()); 
     Console.WriteLine(GenericMethod<IDerived>(foo)); 
    } 

    private static string GenericMethod<T>(object foo) where T : class, IBase 
    { 
     return (foo as T).Method(); 
    } 
} 

Ten kod generuje następujące:

IDerived.Method
IBase.Method

Zamiast tego, co można by oczekiwać:

IDerived.Method
IDerived.Method

Wydaje się, że nie ma mowy (krótka refleksja), aby wywołać ukryte, bardziej wyraźny pochodzący wdrożenie interfejsu typu zdecydowała w czasie wykonywania.

EDIT: Żeby było jasne, co następuje sprawdzenie, czy wartość true w wywołaniu GenericMethod powyżej:

if (typeof (T) == typeof (IDerived))

Tak więc odpowiedź nie jest taka, że ​​T jest zawsze traktowane jako IBase ze względu na ogólne ograniczenie typu "where T: class, IBase".

+0

Dlaczego powinien nazywać się "IDerived.Method"? Poza tym o tej samej nazwie 'IBase.Method' i' IDerived.Method' nie są powiązane. – PetSerAl

+0

Twój "GenericMethod" określa "IBase"? – mxmissile

+0

Jawna implementacja interfejsu jest * wymagana * tutaj, ponieważ metoda Method() jest niejednoznaczna, są dwa z nich. Jeśli podoba Ci się drugi wynik, lepiej ulepszyć swój projekt. Usuń IBase z IDerived lub usuń Method() z IDerived, ponieważ IBase wymaga już wdrożenia. A * single * Method() w klasie implementuje oba interfejsy bez dwuznaczności. –

Odpowiedz

7

Kluczem jest tutaj pamiętać, że IBase.Method i IDerived.Method to dwie zupełnie różne metody. Po prostu podaliśmy im podobne imiona i podpisy. Ponieważ wszystko, co implementuje IDerived, implementuje także IBase, co oznacza, że ​​będą one miały dwie metody o nazwie Method nie pobierające żadnych parametrów. Jeden należy do IDerived i należy do IBase.

Wszystko kompilator wie, podczas kompilowania GenericMethod to, że parametr generycznych będzie stanowić co najmniej IBase, więc może tylko zagwarantować, że realizacja IBase.Method istnieje. Tak nazywa się ta metoda.

W przeciwieństwie do szablonów C++, podstawowa substytucja nie występuje, gdy metoda jest kompilowana (co przy szablonach ma miejsce raz dla każdej kombinacji użytych parametrów szablonu). Zamiast tego metoda jest kompilowana dokładnie raz w taki sposób, że dowolny typ może zostać zastąpiony w środowisku wykonawczym.

W twoim przypadku kompilator emituje IL dla GenericMethod który wygląda mniej więcej tak:

IL_0000: ldarg.0  
IL_0001: isinst  <T> 
IL_0006: unbox.any <T> 
IL_000B: box   <T>  
IL_0010: callvirt IBase.Method 
IL_0015: ret   

zauważyć wyraźnie wywołuje IBase.Method. Nie istnieje żadna wirtualna/nadpisująca relacja między tą metodą a IDerived.Method, więc podstawą jest wszystko, co jest wywoływane, niezależnie od tego, jaki typ zostanie zastąpiony przez T w środowisku wykonawczym.

1

Dodanie do odpowiedzi Kyle'a, której nie mogę zrobić w komentarzu, ponieważ nie mam jeszcze wystarczającej reputacji ...

myślę, że to mówi:

private static string GenericMethod<T>(T foo) where T : class, IBase 
{ 
    return foo.Method() + " " + typeof(T) + " " + typeof(Foo); 
} 

Usuwanie obiektu i mający być parametr T, tak jak odlew jest niepotrzebna nadal wywołuje IBase.Method.

Jestem prawie pewny, że to wszystko bezpośrednio z powodu 4.4.4 Spełnienie ograniczeń w specyfikacji C#.

C# generics nie zachowują się jak szablony C++ w tym zakresie.