Dlaczego metody interfejsu C # nie są zadeklarowane jako abstrakcyjne lub wirtualne?

108

Metody języka C # w interfejsach są deklarowane bez użycia virtualsłowa kluczowego i zastępowane w klasie pochodnej bez użycia overridesłowa kluczowego.

Czy jest tego powód? Zakładam, że to tylko wygoda językowa, a CLR oczywiście wie, jak sobie z tym poradzić pod okładkami (metody nie są domyślnie wirtualne), ale czy są inne techniczne powody?

Oto IL generowana przez klasę pochodną:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

Zwróć uwagę, że metoda jest zadeklarowana virtual finalw IL.

Robert Harvey
źródło

Odpowiedzi:

145

W przypadku interfejsu dodanie słów kluczowych abstractlub nawet publicsłów kluczowych byłoby zbędne, więc je pomijasz:

interface MyInterface {
  void Method();
}

W CIL metoda jest oznaczona virtuali abstract.

(Należy zauważyć, że Java umożliwia deklarowanie elementów interfejsu public abstract).

W przypadku klasy implementującej istnieje kilka opcji:

Niezastąpiona : w C # klasie nie deklaruje metody jako virtual. Oznacza to, że nie można go przesłonić w klasie pochodnej (tylko ukryty). W CIL metoda jest nadal wirtualna (ale zapieczętowana), ponieważ musi obsługiwać polimorfizm dotyczący typu interfejsu.

class MyClass : MyInterface {
  public void Method() {}
}

Zastępowalne : zarówno w C #, jak iw CIL metoda jest virtual. Uczestniczy w wysyłce polimorficznej i może zostać nadpisany.

class MyClass : MyInterface {
  public virtual void Method() {}
}

Jawny : w ten sposób klasa może zaimplementować interfejs, ale nie udostępnia metod interfejsu w interfejsie publicznym samej klasy. W CIL metoda będzie private(!), Ale nadal będzie można ją wywołać spoza klasy z odniesienia do odpowiedniego typu interfejsu. Jawne implementacje również nie podlegają zastąpieniu. Jest to możliwe, ponieważ istnieje dyrektywa CIL ( .override), która połączy metodę prywatną z odpowiednią metodą interfejsu, którą implementuje.

[DO#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

W VB.NET można nawet utworzyć alias nazwy metody interfejsu w klasie implementującej.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

Rozważmy teraz ten dziwny przypadek:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

Jeśli Basei Derivedsą zadeklarowane w tym samym zestawie, kompilator utworzy Base::Methodwirtualny i zapieczętowany (w CIL), mimo żeBase nie implementuje interfejsu.

Jeśli Basei Derivedznajdują się w różnych Derivedzestawach , podczas kompilowania zestawu kompilator nie zmieni innego zestawu, więc wprowadzi element członkowski, Derivedktóry będzie jawną implementacją MyInterface::Method, która po prostu deleguje wywołanie do Base::Method.

Jak widać, każda implementacja metody interfejsu musi obsługiwać zachowanie polimorficzne, a zatem musi być oznaczona jako wirtualna w CIL, nawet jeśli kompilator musi przejść przez obręcze, aby to zrobić.

Jordão
źródło
73

Cytując Jeffreya Ritchera z CLR przez CSharp 3rd Edition tutaj

Środowisko CLR wymaga, aby metody interfejsu były oznaczone jako wirtualne. Jeśli w kodzie źródłowym nie oznaczysz metody jako wirtualnej, kompilator oznaczy ją jako wirtualną i zapieczętowaną; Zapobiega to zastępowaniu metody interfejsu przez klasę pochodną. Jeśli jawnie oznaczysz metodę jako wirtualną, kompilator oznaczy metodę jako wirtualną (i pozostawi ją niezapieczętowaną); pozwala to klasie pochodnej na przesłonięcie metody interfejsu. Jeśli metoda interfejsu jest zapieczętowana, klasa pochodna nie może zastąpić metody. Jednak klasa pochodna może ponownie dziedziczyć ten sam interfejs i może udostępniać własną implementację metod interfejsu.

Usman Khan
źródło
25
Cytat nie wyjaśnia , dlaczego implementacja metody interfejsu musi być oznaczona jako wirtualna. Dzieje się tak, ponieważ jest polimorficzny w odniesieniu do typu interfejsu, więc potrzebuje gniazda w tabeli wirtualnej, aby umożliwić wysyłanie metod wirtualnych.
Jordão
1
Nie wolno mi jawnie oznaczyć metody interfejsu jako wirtualnej i otrzymać błąd „błąd CS0106: modyfikator„ virtual ”nie jest prawidłowy dla tego elementu”. Testowane przy użyciu v2.0.50727 (najstarsza wersja na moim komputerze).
ccppjava,
3
@ccppjava W poniższym komentarzu Jorado zaznaczasz członka klasy, który implementuje interfejs wirtualny, aby umożliwić podklasom nadpisanie klasy.
Christopher Stevenson,
13

Tak, metody implementacji interfejsu są wirtualne, jeśli chodzi o środowisko wykonawcze. Jest to szczegół implementacyjny, który sprawia, że ​​interfejsy działają. Metody wirtualne pobierają sloty w tabeli v-table klasy, każdy slot ma wskaźnik do jednej z metod wirtualnych. Rzutowanie obiektu na typ interfejsu generuje wskaźnik do sekcji tabeli, która implementuje metody interfejsu. Kod klienta, który używa odwołania do interfejsu, widzi teraz pierwszy wskaźnik metody interfejsu na przesunięciu 0 od wskaźnika interfejsu itp.

W mojej pierwotnej odpowiedzi nie doceniłem znaczenia ostatniego atrybutu. Zapobiega zastąpieniu metody wirtualnej przez klasę pochodną. Klasa pochodzi musi CMC interfejs sposoby realizacji cień metod klasy bazowej. To wystarczy, aby zaimplementować kontrakt języka C #, który mówi, że metoda implementacji nie jest wirtualna.

Jeśli zadeklarujesz metodę Dispose () w klasie Example jako wirtualną, zobaczysz, że ostatni atrybut zostanie usunięty. Teraz pozwalam klasie pochodnej na przesłonięcie go.

Hans Passant
źródło
4

W większości innych środowisk kodu skompilowanego interfejsy są implementowane jako vtables - lista wskaźników do treści metod. Zazwyczaj klasa, która implementuje wiele interfejsów, będzie miała gdzieś w swoich wewnętrznych metadanych wygenerowanych przez kompilator listę vtables interfejsów, po jednej tabeli vtable na interfejs (aby zachować kolejność metod). W ten sposób zazwyczaj implementuje się również interfejsy COM.

Jednak w .NET interfejsy nie są implementowane jako oddzielne vtables dla każdej klasy. Metody interfejsu są indeksowane za pośrednictwem globalnej tabeli metod interfejsu, której częścią są wszystkie interfejsy. Dlatego też nie jest konieczne deklarowanie metody wirtualnej, aby ta metoda implementowała metodę interfejsu - tabela metod interfejsu globalnego może po prostu wskazywać bezpośrednio na adres kodowy metody klasy.

Deklarowanie metody wirtualnej w celu zaimplementowania interfejsu nie jest również wymagane w innych językach, nawet na platformach innych niż CLR. Jednym z przykładów jest język Delphi w systemie Win32.

dthorpe
źródło
0

Nie są wirtualne (jeśli chodzi o to, jak o nich myślimy, jeśli nie w kategoriach podstawowej implementacji jako (zapieczętowanej wirtualnej) - dobrze przeczytać inne odpowiedzi tutaj i nauczyć się czegoś sam :-)

Niczego nie zastępują - w interfejsie nie ma implementacji.

Interfejs dostarcza jedynie „kontrakt”, do którego klasa musi się stosować - wzorzec, jeśli chcesz, aby wywołujący wiedzieli, jak wywołać obiekt, nawet jeśli nigdy wcześniej nie widzieli tej konkretnej klasy.

Do klasy należy następnie implementacja metody interfejsu tak, jak to zrobi, w ramach kontraktu - wirtualna lub „niewirtualna” (jak się okazuje zapieczętowana wirtualna).

Jason Williams
źródło
każdy w tym wątku wie, do czego służą interfejsy. Pytanie jest bardzo specyficzne - wygenerowana IL jest wirtualna dla metody interfejsu, a nie wirtualna dla metody bezinterfejsowej.
Rex M
5
Tak, naprawdę łatwo jest skrytykować odpowiedź po zredagowaniu pytania, prawda?
Jason Williams
0

Interfejsy są bardziej abstrakcyjnym pojęciem niż klasy, kiedy deklarujesz klasę, która implementuje interfejs, po prostu mówisz „klasa musi mieć te konkretne metody z interfejsu i nie ma znaczenia, czy statyczne , wirtualne , niewirtualne , nadpisane , jak o ile ma ten sam identyfikator i te same parametry typu ”.

Inne języki obsługujące interfejsy, takie jak Object Pascal („Delphi”) i Objective-C (Mac), nie wymagają oznaczania metod interfejsu jako wirtualnych, a nie wirtualnych.

Ale możesz mieć rację, myślę, że dobrym pomysłem może być posiadanie określonego atrybutu „virtual” / „override” w interfejsach, na wypadek gdybyś chciał ograniczyć metody klas, które implementują określony interfejs. Ale oznacza to również, że dla obu interfejsów mają być słowa kluczowe „nie wirtualne”, „dontcareifvirtual ornot”.

Rozumiem twoje pytanie, ponieważ widzę coś podobnego w Javie, gdy metoda klasowa musi używać „@virtual” lub „@override”, aby upewnić się, że metoda ma być wirtualna.

umlcat
źródło
@override w rzeczywistości nie zmienia zachowania kodu ani nie zmienia wynikowego kodu bajtowego. To, co robi, sygnalizuje kompilatorowi, że tak udekorowana metoda ma być przesłonięciem, co pozwala kompilatorowi na wykonanie pewnych testów poprawności. C # działa inaczej; overridejest pierwszorzędnym słowem kluczowym w samym języku.
Robert Harvey