web-dev-qa-db-fra.com

Pourquoi les méthodes d'interface C # ne sont-elles pas déclarées abstraites ou virtuelles?

Les méthodes C # dans les interfaces sont déclarées sans utiliser le mot clé virtual et remplacées dans la classe dérivée sans utiliser le mot clé override.

Y a-t-il une raison à cela? Je suppose que ce n’est qu’une commodité linguistique, et le CLR sait évidemment comment gérer cela à l’abri (les méthodes ne sont pas virtuelles par défaut), mais existe-t-il d’autres raisons techniques?

Voici l'IL qu'une classe dérivée génère:

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

Notez que la méthode est déclarée virtualfinal dans l'IL.

102
Robert Harvey

Pour l'interface, l'ajout des mots-clés abstract, ou même public serait redondant. Vous devez donc les omettre:

interface MyInterface {
  void Method();
}

Dans le CIL, la méthode est marquée virtual et abstract.

(Notez que Java permet aux membres de l'interface d'être déclarés public abstract).

Pour la classe d'implémentation, il y a quelques options:

Non-substituable: En C #, la classe ne déclare pas la méthode comme virtual. Cela signifie qu'il ne peut pas être remplacé dans une classe dérivée (seulement cachée). Dans le CIL, la méthode est toujours virtuelle (mais scellée) car elle doit prendre en charge le polymorphisme concernant le type d'interface.

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

Overridable: En C # et dans le CIL, la méthode est virtual. Il participe à la répartition polymorphe et peut être remplacé.

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

Explicit: C'est un moyen pour une classe d'implémenter une interface, mais pas de fournir les méthodes d'interface dans l'interface publique de la classe elle-même. Dans le CIL, la méthode sera private (!), Mais il sera toujours possible d'appeler de l'extérieur de la classe à partir d'une référence au type d'interface correspondant. Les implémentations explicites ne sont pas non plus remplaçables. Ceci est possible car il existe une directive CIL (.override) qui liera la méthode privée à la méthode d’interface correspondante qu’elle implémente.

[C #]

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

[CIL]

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

Dans VB.NET, vous pouvez même aliaser le nom de la méthode d'interface dans la classe d'implémentation.

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

Maintenant, considérons ce cas étrange:

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

Si Base et Derived sont déclarés dans le même assemblage, le compilateur fera Base::Method virtuel et scellé (dans le CIL), même si Base n'implémente pas l'interface.

Si Base et Derived se trouvent dans des assemblages différents, lors de la compilation de l'assembly Derived, le compilateur ne modifiera pas l'autre assemblage. Il introduira donc un membre dans Derived ce sera une implémentation explicite pour MyInterface::Method qui ne fera que déléguer l'appel à Base::Method.

Vous voyez donc chaque implémentation de la méthode d'interface doit prendre en charge le comportement polymorphe, et doit donc être marquée comme virtuelle sur le CIL, même si le compilateur doit effectuer des étapes pour fais le.

140
Jordão

Citation de Jeffrey Ritcher de CLR via CSharp 3rd Edition ici

Le CLR nécessite que les méthodes d'interface soient marquées comme virtuelles. Si vous ne marquez pas explicitement la méthode comme virtuelle dans votre code source, le compilateur marque la méthode comme virtuelle et scellée; Cela empêche une classe dérivée de remplacer la méthode d'interface. Si vous marquez explicitement la méthode comme virtuelle, le compilateur la marque comme virtuelle (et la laisse non scellée); Cela permet à une classe dérivée de remplacer la méthode d'interface. Si une méthode d'interface est scellée, une classe dérivée ne peut pas remplacer la méthode. Cependant, une classe dérivée peut ré-hériter de la même interface et peut fournir sa propre implémentation pour les méthodes de l’interface.

72
Usman Khan

Oui, les méthodes d'implémentation d'interface sont virtuelles en ce qui concerne l'exécution. C'est un détail d'implémentation, cela fait fonctionner les interfaces. Les méthodes virtuelles obtiennent des emplacements dans la table v de la classe, chaque emplacement ayant un pointeur sur l'une des méthodes virtuelles. La conversion d'un objet en un type d'interface génère un pointeur sur la section de la table qui implémente les méthodes d'interface. Le code client qui utilise la référence d'interface voit maintenant le premier pointeur de méthode d'interface situé à l'offset 0 du pointeur d'interface, etc.

Ce que j'ai sous-estimé dans ma réponse initiale, c'est la signification de l'attribut final. Cela empêche une classe dérivée de remplacer la méthode virtuelle. Une classe dérivée doit ré-implémenter l'interface, les méthodes d'implémentation shadow les méthodes de la classe de base. Ce qui est suffisant pour implémenter le contrat de langage C # qui dit que la méthode d'implémentation n'est pas virtuelle.

Si vous déclarez la méthode Dispose () dans la classe Example comme virtuelle, l'attribut final sera supprimé. Permettant maintenant à une classe dérivée de la remplacer.

12
Hans Passant

Dans la plupart des autres environnements de code compilés, les interfaces sont implémentées en tant que vtables - une liste de pointeurs sur le corps des méthodes. En règle générale, une classe qui implémente plusieurs interfaces aura quelque part dans ses métadonnées générées par le compilateur interne une liste de vtables d'interface, une vtable par interface (afin que l'ordre des méthodes soit préservé). C’est ainsi que les interfaces COM sont généralement implémentées.

Dans .NET cependant, les interfaces ne sont pas implémentées en tant que vtables distinctes pour chaque classe. Les méthodes d'interface sont indexées via une table de méthodes d'interface globale à laquelle toutes les interfaces font partie. Par conséquent, il n'est pas nécessaire de déclarer une méthode virtuelle pour que cette méthode implémente une méthode d'interface - la table de méthode d'interface globale peut simplement pointer directement sur l'adresse de code de la méthode de la classe.

Déclarer une méthode virtuelle pour implémenter une interface n'est pas nécessaire non plus dans d'autres langues, même sur des plates-formes non CLR. Le langage Delphi sur Win32 en est un exemple.

4
dthorpe

Elles ne sont pas virtuelles (en termes de comment nous les pensons, sinon en termes d'implémentation sous-jacente comme (scellé virtuel) - il est bon de lire les autres réponses ici et d'apprendre quelque chose moi-même :-)

Ils ne remplacent rien - il n'y a pas d'implémentation dans l'interface.

Toute l'interface fournit un "contrat" ​​auquel la classe doit adhérer - un modèle, si vous voulez, afin que les appelants sachent comment appeler l'objet même s'ils n'ont jamais vu cette classe en particulier auparavant.

Il appartient à la classe d'implémenter ensuite la méthode d'interface comme elle l'entendra, dans les limites du contrat - virtuelle ou "non virtuelle" (virtuelle scellée, comme il s'avère).

0
Jason Williams

<blague> C’est quelque chose que vous voudrez peut-être demander à Anders Hejlsberg et au reste de l’équipe de conception C #. </ joke>

Les interfaces sont un concept plus abstrait que les classes, lorsque vous déclarez une classe qui implémente une interface, vous dites simplement "la classe doit avoir ces méthodes particulières à partir de l'interface, et cela n'a pas d'importance si statique , virtuel , non virtuel , overriden , tant qu'il a le même identifiant et les mêmes paramètres de type ".

D'autres langages prenant en charge des interfaces telles que Object Pascal ("Delphi") et Objective-C (Mac) n'exigent pas que les méthodes d'interface soient marquées comme étant virtuelles et non virtuelles.

Mais, vous avez peut-être raison, je pense que c'est une bonne idée d'avoir un attribut spécifique "virtuel"/"override" dans les interfaces, au cas où vous voudriez restreindre les méthodes de classes qui implémentent une interface particulière . Mais cela signifie également que les deux interfaces ont un mot-clé "non-virtuel", "dont soinifvirtualornot".

Je comprends votre question, car je vois quelque chose de similaire en Java, quand une méthode de classe doit utiliser "@virtual" ou "@override" pour être sûre qu’une méthode se veut virtuelle.

0
umlcat