Warum werden C # -Schnittstellenmethoden nicht als abstrakt oder virtuell deklariert?

108

C # -Methoden in Schnittstellen werden ohne Verwendung des virtualSchlüsselworts deklariert und in der abgeleiteten Klasse ohne Verwendung des overrideSchlüsselworts überschrieben .

Gibt es einen Grund dafür? Ich gehe davon aus, dass dies nur eine Annehmlichkeit für die Sprache ist, und offensichtlich weiß die CLR, wie man dies unter dem Deckmantel handhabt (Methoden sind standardmäßig nicht virtuell), aber gibt es andere technische Gründe?

Hier ist die IL, die eine abgeleitete Klasse generiert:

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

Beachten Sie, dass die Methode virtual finalin der IL deklariert ist .

Robert Harvey
quelle

Antworten:

145

Für die Benutzeroberfläche wäre das Hinzufügen der abstractoder sogar der publicSchlüsselwörter redundant, sodass Sie sie weglassen:

interface MyInterface {
  void Method();
}

In der CIL ist die Methode markiert virtualund abstract.

(Beachten Sie, dass mit Java Schnittstellenmitglieder deklariert werden können. public abstract)

Für die implementierende Klasse gibt es einige Optionen:

Nicht überschreibbar : In C # deklariert die Klasse die Methode nicht als virtual. Das bedeutet, dass es in einer abgeleiteten Klasse nicht überschrieben werden kann (nur versteckt). In der CIL ist die Methode immer noch virtuell (aber versiegelt), da sie den Polymorphismus hinsichtlich des Schnittstellentyps unterstützen muss.

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

Overridable : Sowohl in C # als auch in CIL ist die Methode virtual. Es nimmt am polymorphen Versand teil und kann überschrieben werden.

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

Explizit : Dies ist eine Möglichkeit für eine Klasse, eine Schnittstelle zu implementieren, jedoch nicht die Schnittstellenmethoden in der öffentlichen Schnittstelle der Klasse selbst bereitzustellen. In der CIL ist die Methode private(!), Kann jedoch weiterhin von außerhalb der Klasse über einen Verweis auf den entsprechenden Schnittstellentyp aufgerufen werden. Explizite Implementierungen können ebenfalls nicht überschrieben werden. Dies ist möglich, weil es eine CIL-Direktive ( .override) gibt, die die private Methode mit der entsprechenden Schnittstellenmethode verknüpft, die sie implementiert.

[C #]

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

[CIL]

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

In VB.NET können Sie sogar den Namen der Schnittstellenmethode in der implementierenden Klasse als Alias ​​verwenden.

[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
}

Betrachten Sie nun diesen seltsamen Fall:

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

Wenn Baseund Derivedin derselben Assembly deklariert sind, wird der Compiler Base::Methodvirtuell und versiegelt (in der CIL), obwohl Basedie Schnittstelle nicht implementiert wird.

Wenn Baseund Derivedsich in verschiedenen Assemblys befinden, Derivedändert der Compiler beim Kompilieren der Assembly nicht die andere Assembly, sodass ein Mitglied eingeführt Derivedwird, das eine explizite Implementierung darstellt MyInterface::Method, an die der Aufruf lediglich delegiert wird Base::Method.

Sie sehen also, jede Implementierung der Schnittstellenmethode muss polymorphes Verhalten unterstützen und daher in der CIL als virtuell markiert sein, selbst wenn der Compiler dafür Hoops durchlaufen muss.

Jordão
quelle
73

Zitiert Jeffrey Ritcher von CLR über CSharp 3rd Edition hier

Die CLR erfordert, dass Schnittstellenmethoden als virtuell markiert werden. Wenn Sie die Methode in Ihrem Quellcode nicht explizit als virtuell markieren, markiert der Compiler die Methode als virtuell und versiegelt. Dies verhindert, dass eine abgeleitete Klasse die Schnittstellenmethode überschreibt. Wenn Sie die Methode explizit als virtuell markieren, markiert der Compiler die Methode als virtuell (und lässt sie nicht versiegelt). Dadurch kann eine abgeleitete Klasse die Schnittstellenmethode überschreiben. Wenn eine Schnittstellenmethode versiegelt ist, kann eine abgeleitete Klasse die Methode nicht überschreiben. Eine abgeleitete Klasse kann jedoch dieselbe Schnittstelle erneut erben und eine eigene Implementierung für die Methoden der Schnittstelle bereitstellen.

Usman Khan
quelle
25
Das Zitat sagt nicht aus, warum eine Implementierung einer Schnittstellenmethode als virtuell markiert werden muss. Es liegt daran , dass es in Bezug auf den Schnittstellentyp polymorphen ist, so dass es braucht einen Steckplatz auf der virtuellen Tabelle virtuelle Methode Dispatching zu ermöglichen.
Jordão
1
Ich darf die Schnittstellenmethode nicht explizit als virtuell markieren und erhalte die Fehlermeldung "Fehler CS0106: Der Modifikator 'virtuell' ist für dieses Element nicht gültig". Getestet mit v2.0.50727 (älteste Version auf meinem PC).
ccppjava
3
@ccppjava In Jorados Kommentar unten markieren Sie das Klassenmitglied, das die virtuelle Schnittstelle implementiert, damit Unterklassen die Klasse überschreiben können.
Christopher Stevenson
13

Ja, Schnittstellenimplementierungsmethoden sind hinsichtlich der Laufzeit virtuell. Es ist ein Implementierungsdetail, mit dem Schnittstellen funktionieren. Virtuelle Methoden erhalten Slots in der V-Tabelle der Klasse. Jeder Slot hat einen Zeiger auf eine der virtuellen Methoden. Durch das Umwandeln eines Objekts in einen Schnittstellentyp wird ein Zeiger auf den Abschnitt der Tabelle generiert, der die Schnittstellenmethoden implementiert. Der Client-Code, der die Schnittstellenreferenz verwendet, sieht jetzt den ersten Schnittstellenmethodenzeiger mit dem Offset 0 vom Schnittstellenzeiger usw.

Was ich in meiner ursprünglichen Antwort unterschätzt habe, ist die Bedeutung des endgültigen Attributs. Es verhindert, dass eine abgeleitete Klasse die virtuelle Methode überschreibt. Eine abgeleitete Klasse muss die Schnittstelle erneut implementieren. Die Implementierungsmethoden überschatten die Basisklassenmethoden. Dies reicht aus, um den C # -Sprachenvertrag zu implementieren, der besagt, dass die Implementierungsmethode nicht virtuell ist.

Wenn Sie die Dispose () -Methode in der Example-Klasse als virtuell deklarieren, wird das endgültige Attribut entfernt. Jetzt kann eine abgeleitete Klasse sie überschreiben.

Hans Passant
quelle
4

In den meisten anderen kompilierten Codeumgebungen werden Schnittstellen als vtables implementiert - eine Liste von Zeigern auf die Methodenkörper. Normalerweise hat eine Klasse, die mehrere Schnittstellen implementiert, irgendwo in ihrem internen Compiler generierte Metadaten eine Liste von Schnittstellen-Vtables, eine Vtable pro Schnittstelle (damit die Methodenreihenfolge erhalten bleibt). Auf diese Weise werden in der Regel auch COM-Schnittstellen implementiert.

In .NET werden Schnittstellen jedoch nicht als separate vtables für jede Klasse implementiert. Schnittstellenmethoden werden über eine globale Schnittstellenmethodentabelle indiziert, zu der alle Schnittstellen gehören. Daher ist es nicht erforderlich, eine Methode als virtuell zu deklarieren, damit diese Methode eine Schnittstellenmethode implementiert. Die globale Schnittstellenmethoden-Tabelle kann nur direkt auf die Codeadresse der Klassenmethode verweisen.

Das Deklarieren einer virtuellen Methode zum Implementieren einer Schnittstelle ist auch in anderen Sprachen nicht erforderlich, selbst auf Nicht-CLR-Plattformen. Die Delphi-Sprache unter Win32 ist ein Beispiel.

dthorpe
quelle
0

Sie sind nicht virtuell (in Bezug darauf, wie wir sie sehen, wenn nicht in Bezug auf die zugrunde liegende Implementierung als (versiegelt virtuell) - gut, die anderen Antworten hier zu lesen und selbst etwas zu lernen :-)

Sie überschreiben nichts - es gibt keine Implementierung in der Schnittstelle.

Die Schnittstelle liefert lediglich einen "Vertrag", an den sich die Klasse halten muss - ein Muster, wenn Sie möchten, damit Aufrufer wissen, wie sie das Objekt aufrufen, selbst wenn sie diese bestimmte Klasse noch nie gesehen haben.

Es liegt an der Klasse, die Schnittstellenmethode innerhalb der Vertragsgrenzen wie gewünscht zu implementieren - virtuell oder "nicht virtuell" (wie sich herausstellt, versiegelt virtuell).

Jason Williams
quelle
Jeder in diesem Thread weiß, wofür Schnittstellen sind. Die Frage ist äußerst spezifisch: Die generierte IL ist für die Schnittstellenmethode virtuell und für eine Nicht-Schnittstellenmethode nicht virtuell.
Rex M
5
Ja, es ist wirklich einfach, eine Antwort zu kritisieren, nachdem die Frage bearbeitet wurde, nicht wahr?
Jason Williams
0

Schnittstellen sind ein abstrakteres Konzept als Klassen. Wenn Sie eine Klasse deklarieren, die eine Schnittstelle implementiert, sagen Sie nur: "Die Klasse muss diese bestimmten Methoden von der Schnittstelle haben und spielt keine Rolle, ob statisch , virtuell , nicht virtuell , überschrieben , als solange es die gleiche ID und die gleichen Typparameter hat ".

Andere Sprachen, die Schnittstellen wie Object Pascal ("Delphi") und Objective-C (Mac) unterstützen, erfordern nicht, dass Schnittstellenmethoden als virtuell und nicht als virtuell markiert werden.

Aber Sie haben vielleicht Recht, ich denke, es ist eine gute Idee, ein bestimmtes Attribut "virtuell" / "überschreiben" in Schnittstellen zu haben, falls Sie die Klassenmethoden einschränken möchten , die eine bestimmte Schnittstelle implementieren. Dies bedeutet jedoch auch, dass für beide Schnittstellen die Schlüsselwörter "nicht virtuell" und "nicht pflegebedürftig" nicht vorhanden sind.

Ich verstehe Ihre Frage, weil ich in Java etwas Ähnliches sehe, wenn eine Klassenmethode "@virtual" oder "@override" verwenden muss, um sicherzugehen, dass eine Methode virtuell sein soll.

umlcat
quelle
@override ändert weder das Verhalten des Codes noch den resultierenden Bytecode. Es signalisiert dem Compiler, dass die so dekorierte Methode eine Überschreibung sein soll, die es dem Compiler ermöglicht, einige Überprüfungen der Integrität durchzuführen. C # funktioniert anders;overrideist ein erstklassiges Schlüsselwort in der Sprache selbst.
Robert Harvey