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 c’est juste un langage pratique, et le CLR sait évidemment 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 génération 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 final virtual dans l’IL.

Pour l’interface, l’ajout de l’ abstract ou même public mots clés public serait redondant, vous les omettez donc:

 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 comme public abstract ).

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

Non remplaçable : 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 prendre en charge le polymorphism concernant le type d’interface.

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

Irrégulable : à la fois 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 : Ceci permet à une classe d’implémenter une interface sans fournir les méthodes d’interface dans l’interface publique de la classe. Dans le CIL, la méthode sera private (!) Mais sera toujours appelable en dehors de la classe à partir d’une référence au type d’interface correspondant. Les implémentations explicites sont également non remplaçables. Ceci est possible car il existe une directive CIL ( .override ) qui lie 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 alias 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 assembly, le compilateur rendra Base::Method virtuel et scellé (dans le CIL), même si Base n’implémente pas l’interface.

Si Base et Derived sont dans des assemblys différents, lors de la compilation de l’assemblage Derived , le compilateur ne changera pas l’autre assemblage, il introduira donc un membre dans Derived qui sera une implémentation explicite pour MyInterface::Method qui déléguera l’appel à Base::Method .

Comme vous le 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 obstacles pour le faire.

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 de la même interface et peut fournir sa propre implémentation pour les méthodes de l’interface.

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, il fait fonctionner les interfaces. Les méthodes virtuelles obtiennent des slots dans la v-table de la classe, chaque slot a un pointeur sur l’une des méthodes virtuelles. Lancer un object sur 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 à l’offset 0 du pointeur d’interface, etc.

Ce que j’ai sous-estimé dans ma réponse initiale est la signification de l’atsortingbut 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 masquent 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 étant virtuelle, vous verrez que l’atsortingbut final sera supprimé. Autoriser maintenant une classe dérivée à la remplacer.

Dans la plupart des autres environnements de code compilés, les interfaces sont implémentées en tant que vtables – une liste de pointeurs vers les corps de méthode. Typiquement, une classe qui implémente plusieurs interfaces aura quelque part dans son méta-donnée interne générée par le compilateur une liste de vtables d’interface, une vtable par interface (de sorte 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 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.

La déclaration d’une méthode virtuelle pour implémenter une interface n’est pas non plus requirejse dans d’autres langages, même dans les plates-formes non-CLR. Le langage Delphi sur Win32 en est un exemple.

Ils ne sont pas virtuels (pour ce qui est de leur conception, sinon de leur implémentation sous-jacente (virtuelle scellée)).

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

Tout ce que l’interface fait, c’est fournir un “contrat” ​​auquel la classe doit adhérer – un modèle, si vous voulez, pour que les appelants sachent comment appeler l’object même s’ils n’ont jamais vu cette classe particulière auparavant.

Il appartient alors à la classe d’implémenter la méthode de l’interface telle quelle, dans les limites du contrat – virtuelle ou «non virtuelle» (virtuelle scellée en l’état).

C’est quelque chose que vous voudrez peut-être demander à Anders Hejlsberg et au rest de l’équipe de conception de C #.

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 qu’elle soit statique , virtuelle , non virtuelle , remplacée , longtemps qu’il a le même identifiant et les mêmes parameters de type “.

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

Mais, vous avez peut-être raison, je pense que ce pourrait être une bonne idée d’avoir un atsortingbut “virtuel” / “remplacement” spécifique 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, quand une méthode de classe doit utiliser “@virtual” ou “@override” pour être sûr qu’une méthode est censée être virtuelle.