webentwicklung-frage-antwort-db.com.de

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

C # -Methoden in Interfaces werden ohne das Schlüsselwort virtual deklariert und in der abgeleiteten Klasse ohne das Schlüsselwort override überschrieben.

Gibt es einen Grund dafür? Ich gehe davon aus, dass es nur eine sprachliche Annehmlichkeit ist, und offensichtlich weiß die CLR, wie sie damit umgeht (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 in der IL als virtualfinal deklariert ist.

102
Robert Harvey

Für die Schnittstelle wäre das Hinzufügen der Schlüsselwörter abstract oder sogar public überflüssig, sodass Sie sie weglassen:

interface MyInterface {
  void Method();
}

In der CIL ist die Methode mit virtual und abstract markiert.

(Beachten Sie, dass Java ermöglicht, dass Schnittstellenmitglieder deklariert werden 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 ausgeblendet). In der CIL ist die Methode noch virtuell (aber versiegelt), da sie den Polymorphismus in Bezug auf den Schnittstellentyp unterstützen muss.

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

Overridable: Sowohl in C # als auch in der CIL lautet 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, die Schnittstellenmethoden jedoch nicht in der öffentlichen Schnittstelle der Klasse selbst bereitzustellen. In der CIL lautet die Methode private (!), Sie kann jedoch weiterhin von außerhalb der Klasse von einem 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 implementierten Schnittstellenmethode verknüpft.

[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 aliasisieren.

[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 Base und Derived in derselben Assembly deklariert sind, macht der Compiler Base::Method Virtuell und versiegelt (in der CIL), obwohl Base nicht ' t Implementieren Sie die Schnittstelle.

Wenn sich Base und Derived in verschiedenen Assemblys befinden, ändert der Compiler beim Kompilieren der Derived Assembly nicht die andere Assembly, sodass ein Member in Derived ist eine explizite Implementierung für MyInterface::Method, die den Aufruf nur an Base::Method delegiert.

Sie sehen also, dass jede Implementierung einer Schnittstellenmethode polymorphes Verhalten unterstützen muss und daher in der CIL als virtuell markiert sein muss, auch wenn der Compiler durch die Rahmen von to gehen muss TU es.

140
Jordão

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

Die CLR erfordert, dass Schnittstellenmethoden als virtuell markiert sind. Wenn Sie die Methode in Ihrem Quellcode nicht explizit als virtuell markieren, markiert der Compiler die Methode als virtuell und versiegelt. Dadurch wird 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 unversiegelt). 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.

72
Usman Khan

Ja, Schnittstellenimplementierungsmethoden sind für die Laufzeit virtuell. Es ist ein Implementierungsdetail, es lässt Schnittstellen funktionieren. Virtuelle Methoden erhalten Slots in der V-Tabelle der Klasse. Jeder Slot hat einen Zeiger auf eine der virtuellen Methoden. Wenn Sie ein Objekt in einen Schnittstellentyp umwandeln, 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 bei Offset 0 vom Schnittstellenzeiger usw..

Was ich in meiner ursprünglichen Antwort unterschätzt habe, ist die Bedeutung des final Attributs. Es verhindert, dass eine abgeleitete Klasse die virtuelle Methode überschreibt. Eine abgeleitete Klasse muss die Schnittstelle erneut implementieren, die Implementierungsmethoden shadow 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 final -Attribut entfernt. Jetzt kann eine abgeleitete Klasse sie überschreiben.

12
Hans Passant

In den meisten anderen kompilierten Codeumgebungen werden Schnittstellen als vtables implementiert - eine Liste von Zeigern auf die Methodenkörper. In der Regel enthält eine Klasse, die mehrere Schnittstellen implementiert, in ihren vom internen Compiler generierten Metadaten eine Liste von vtables der Schnittstelle, 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 für jede Klasse als separate vtables implementiert. Schnittstellenmethoden werden über eine globale Schnittstellenmethodentabelle indiziert, zu der alle Schnittstellen gehören. Es ist daher nicht erforderlich, eine Methode als virtuell zu deklarieren, damit diese Methode eine Schnittstellenmethode implementiert. Die globale Schnittstellenmethodentabelle kann lediglich 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.

4
dthorpe

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 die Aufrufer wissen, wie sie das Objekt aufrufen können, selbst wenn sie diese bestimmte Klasse noch nie zuvor gesehen haben.

Es ist Aufgabe der Klasse, die Schnittstellenmethode innerhalb der Grenzen des Vertrags wie gewünscht zu implementieren - virtuell oder "nicht virtuell" (versiegelt virtuell, wie sich herausstellt).

0
Jason Williams

<joke> Dies ist etwas, das Sie möglicherweise an Anders Hejlsberg und den Rest des C # -Designteams richten möchten. </ joke>

Interfaces sind abstrakter als Klassen. Wenn Sie eine Klasse deklarieren, die ein Interface implementiert, sagen Sie nur: "Die Klasse muss diese bestimmten Methoden aus dem Interface haben und es spielt keine Rolle, ob statisch ist , virtuell , nicht virtuell , überschreiben , solange sie dieselbe ID und dieselben Typparameter haben ".

Für andere Sprachen, die Schnittstellen wie Object Pascal ("Delphi") und Objective-C (Mac) unterstützen, müssen Schnittstellenmethoden nicht als virtuell und auch nicht als virtuell markiert werden.

Aber vielleicht haben Sie recht, ich denke, es ist eine gute Idee, ein bestimmtes "virtuelles"/"Überschreiben" -Attribut in den Schnittstellen zu haben, falls Sie einschränken wollen die Klassenmethoden, die ein implementieren bestimmte Schnittstelle. Das bedeutet aber auch, für beide Schnittstellen ein "nicht-virtuelles", "nicht-virtuelles" Schlüsselwort zu haben.

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

0
umlcat