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

108

Les méthodes C # des interfaces sont déclarées sans utiliser le virtualmot - clé et remplacées dans la classe dérivée sans utiliser le overridemot - clé.

Y a-t-il une raison à cela? Je suppose que c'est juste une commodité de langage, et évidemment le CLR sait comment gérer cela sous les couvertures (les méthodes ne sont pas virtuelles par défaut), mais y a-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 virtual finaldans l'IL.

Robert Harvey
la source

Réponses:

145

Pour l'interface, l'ajout du abstract, voire des publicmots - clés serait redondant, vous les omettez donc:

interface MyInterface {
  void Method();
}

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

(Notez que Java autorise la déclaration des membres de l'interface public abstract).

Pour la classe d'implémentation, il existe 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 (uniquement masquée). Dans le CIL, la méthode est toujours virtuelle (mais scellée) car elle doit supporter le polymorphisme concernant le type d'interface.

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

Remplaçable : à la fois en C # et en CIL, la méthode est virtual. Il participe à l'envoi polymorphe et peut être remplacé.

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

Explicite : C'est un moyen pour une classe d'implémenter une interface mais de ne pas 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 elle sera toujours appelable de l'extérieur de la classe à partir d'une référence au type d'interface correspondant. Les implémentations explicites ne peuvent pas non plus être remplacées. Cela 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érez ce cas étrange:

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

Si Baseet Derivedsont déclarés dans le même assembly, le compilateur rendra Base::Methodvirtuel et scellé (dans le CIL), même s'il Basen'implémente pas l'interface.

Si Baseet Derivedsont dans des assemblys différents, lors de la compilation de l' Derivedassembly, le compilateur ne changera pas l'autre assembly, il introduira donc un membre dans Derivedqui sera une implémentation explicite car MyInterface::Methodcela déléguera simplement l'appel à Base::Method.

Ainsi, vous voyez, chaque implémentation de méthode d'interface doit prendre en charge le comportement polymorphe, et doit donc être marquée virtuelle sur le CIL, même si le compilateur doit passer par des cercles pour le faire.

Jordão
la source
73

Citant Jeffrey Ritcher de CLR via CSharp 3rd Edition ici

Le CLR exige 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 marque la méthode 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 hériter à nouveau de la même interface et peut fournir sa propre implémentation pour les méthodes de l'interface.

Usman Khan
la source
25
La citation ne dit pas pourquoi une implémentation de méthode d'interface doit être marquée comme virtuelle. C'est parce qu'il est polymorphe en ce qui concerne le type d'interface, il a donc besoin d' un emplacement sur la table virtuelle pour permettre la distribution de méthode virtuelle.
Jordão
1
Je ne suis pas autorisé à marquer explicitement la méthode d'interface comme virtuelle et à obtenir l'erreur "erreur CS0106: Le modificateur" virtuel "n'est pas valide pour cet élément". Testé avec la version 2.0.50727 (la version la plus ancienne de mon PC).
ccppjava
3
@ccppjava À partir du commentaire de Jorado ci-dessous, vous marquez le membre de classe qui implémente l'interface virtuelle pour permettre aux sous-classes de remplacer la classe.
Christopher Stevenson
13

Oui, les méthodes d'implémentation d'interface sont virtuelles en ce qui concerne le runtime. C'est un détail d'implémentation, cela fait fonctionner les interfaces. Les méthodes virtuelles obtiennent des emplacements dans la v-table de la classe, chaque emplacement a un pointeur vers l'une des méthodes virtuelles. Le cast d'un objet en type d'interface génère un pointeur vers 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 à 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 . Il 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 reflètent les méthodes de 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, vous verrez l' attribut final être supprimé. Permettre maintenant à une classe dérivée de la remplacer.

Hans Passant
la source
4

Dans la plupart des autres environnements de code compilé, les interfaces sont implémentées sous forme de vtables - une liste de pointeurs vers les corps de méthode. En général, 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 dont toutes les interfaces font partie. Par conséquent, il n'est pas nécessaire de déclarer une méthode virtual pour que cette méthode implémente une méthode d'interface - la table de méthode d'interface globale peut simplement pointer directement vers 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 non plus nécessaire dans d'autres langages, même dans les plates-formes non CLR. Le langage Delphi sur Win32 en est un exemple.

dthorpe
la source
0

Ils ne sont pas virtuels (en termes de façon dont nous pensons à eux, sinon en termes de mise en œuvre sous-jacente en tant que (virtuel scellé) - 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 aucune implémentation dans l'interface.

L'interface ne fait que fournir un "contrat" ​​auquel la classe doit adhérer - un modèle, si vous le souhaitez, afin que les appelants sachent comment appeler l'objet même s'ils n'ont jamais vu cette classe particulière auparavant.

Il appartient à la classe d'implémenter ensuite la méthode d'interface comme elle le fera, dans les limites du contrat - virtuel ou «non virtuel» (virtuel scellé en fait).

Jason Williams
la source
tout le monde dans ce fil sait à quoi servent les interfaces. La question est extrêmement spécifique - l'IL généré est virtuel pour la méthode d'interface et non virtuel pour une méthode non-interface.
Rex M
5
Oui, il est vraiment facile de critiquer une réponse après que la question a été modifiée, n'est-ce pas?
Jason Williams
0

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 de l'interface, et peu importe qu'elle soit statique , virtuelle , non virtuelle , surchargée , comme tant qu'il a le même ID et les mêmes paramètres de type ".

D'autres langages qui prennent en charge des interfaces comme Object Pascal ("Delphi") et Objective-C (Mac) ne nécessitent pas non plus que les méthodes d'interface soient marquées comme virtuelles et non virtuelles.

Mais, vous avez peut-être raison, je pense que c'est peut-être 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 aussi avoir un mot-clé «non virtuel», «dontcareifvirtualornot», pour les deux interfaces.

Je comprends votre question, car je vois quelque chose de similaire en Java, lorsqu'une méthode de classe doit utiliser le "@virtual" ou "@override" pour être sûr qu'une méthode est censée être virtuelle.

umlcat
la source
@override ne change pas réellement le comportement du code ou ne modifie pas le code d'octet résultant. Il signale au compilateur que la méthode ainsi décorée est destinée à être un remplacement, ce qui permet au compilateur de faire des vérifications de cohérence. C # fonctionne différemment; overrideest un mot-clé de première classe dans la langue elle-même.
Robert Harvey le