Plus d’informations sur Virtual / new… plus interfaces!

Hier, j’ai posté une question sur les nouveaux mots clés / virtuel / outrepasser, et j’ai beaucoup appris de vos réponses. Mais je rest avec quelques doutes.

Entre toutes les “boîtes”, j’ai perdu le contact avec ce qui se passe réellement dans les tables de méthodes du type. Par exemple:

interface I1 { void Draw(); } interface I2 { void Draw(); } class A : I1, I2 { public void Minstance() { Console.WriteLine("A::MInstance"); } public virtual void Draw() { Console.WriteLine("A::Draw"); } void I2.Draw() { Console.WriteLine("A::I2.Draw"); } } class B : A, I1, I2 { public new virtual void Draw() { Console.WriteLine("B::Draw"); } void I1.Draw() { Console.WriteLine("B::I1.Draw"); } } class Test { public static void Main() { A a = new B(); a.Draw(); I1 i1 = new A(); i1.Draw(); I2 i2 = new B(); i2.Draw(); B b = (B)a; b.Draw(); } } } 

La question posée dans cet exercice est la suivante: Complétez les tables de méthodes de types en fonction du code et expliquez la sortie générée en exécutant Main ().

Ma réponse était: Dans le type A, nous avons 3 méthodes: MInstance (), Draw () – la version A :: Draw – et I2 :: Draw Dans le type B, nous avons 4 méthodes: MInstance de A, B :: Draw, I1 :: Draw et I2 :: Draw

Je ne suis pas très confiant quant à ma réponse, et c’est pourquoi je poste cette question. Lorsque nous implémentons des interfaces, un nouvel emplacement sur la table de méthodes est créé pour les méthodes de ladite interface. ne devrions-nous pas implémenter I2 :: Draw aussi en classe A?

De même, lorsque nous appelons une méthode à l’aide d’une variable d’interface (telle que i1.Draw ()), nous comprenons que nous sums sur une répartition dynamic. Par conséquent, nous devons examiner le type de l’object détenu par la variable (type A dans ce cas ) et recherchez dans la table des méthodes de A une méthode appelée spécifiquement I1.Draw. Mais si on ne le trouve pas? Comment dois-je procéder dans ces cas? Existe-t-il une règle empirique que je devrais connaître pour pouvoir résoudre ces problèmes avec succès?

Désolé d’être si ennuyeux avec cette question, mais j’ai vraiment besoin de dénouer ce nœud sur ma tête;)

À votre santé!

Bonne question.

La façon de penser à ceci est: les interfaces obtiennent leur propre ensemble de slots. Une classe qui implémente une interface est nécessaire pour remplir ces espaces.

  • L’interface I1 a un emplacement appelé I1SLOT.
  • L’interface I1 a un emplacement appelé I2SLOT.
  • La classe A possède deux emplacements, AMinSLOT et ADrawSLOT.
  • La classe A comporte trois méthodes, appelées AMinMethod, ADrawMethod et AI2DrawMethod.
  • Lorsque vous dites “nouveau A”, le moteur d’exécution dispose de quatre emplacements à remplir.
  • I1SLOT est rempli avec ADrawMethod.
  • I2SLOT est rempli avec AI2DrawMethod.
  • AMinSLOT est rempli avec AMinMethod.
  • ADrawSLOT est rempli avec ADrawMethod.
  • La classe B a trois emplacements. Il hérite de AMinSLOT et ADrawSLOT et définit un nouvel emplacement, BDrawSLOT.
  • La classe B a deux méthodes, BDrawMethod et BI1DrawMethod.
  • Lorsque vous dites “nouveau B”, le moteur d’exécution dispose de cinq emplacements à remplir.
  • I1SLOT est rempli avec BI1DrawMethod.
  • I2SLOT est rempli avec BDrawMethod.
  • AMinSLOT est rempli avec AMinMethod.
  • ADrawSLOT est rempli avec ADrawMethod.
  • BDrawSLOT est rempli avec BDrawMethod.

Rappelez-vous que le problème de la résolution de surcharge consiste à choisir l’emplacement en fonction du type et des arguments. Il n’y a pas d’argument, le compilateur n’a donc que le type de départ.

  • Lorsque vous appelez Draw sur un object de type compilation A, la meilleure correspondance est ADrawSLOT.
  • Lorsque vous appelez Draw sur un object de type compilation B, BDrawSLOT constitue la meilleure correspondance.
  • Lorsque vous appelez Draw sur un object de type compilation I1, la meilleure correspondance est I1SLOT.
  • Lorsque vous appelez Draw sur un object de type compilation I2, la meilleure correspondance est I2SLOT.

Et le compilateur génère un code qui dit “appelle quelle que soit la méthode qui se trouve dans l’emplacement choisi au moment de l’exécution”.

En résumé:

 A a1 = new A(); A a2 = new B(); B b = new B(); (a1 as A).Draw(); // ADrawSLOT contains A::Draw (a1 as I1).Draw(); // I1SLOT contains A::Draw (a1 as I2).Draw(); // I2SLOT contains A::I2.Draw (a2 as A).Draw(); // ADrawSLOT contains A::Draw (a2 as B).Draw(); // BDrawSLOT contains B::Draw (a2 as I1).Draw(); // I1SLOT contains B::I1.Draw (a2 as I2).Draw(); // I2SLOT contains B::Draw (b as A).Draw(); // ADrawSLOT contains A::Draw (b as B).Draw(); // BDrawSLOT contains B::Draw (b as I1).Draw(); // I1SLOT contains B::I1Draw (b as I2).Draw(); // I2SLOT contains B::Draw 

Si vous souhaitez savoir comment cela est implémenté, utilisez ILDASM pour désassembler votre programme, puis consultez la table de métadonnées 25 (0x19), la table MethodImpl. MethodImplTable de ce programme est:

 1 == 0:TypeDef[2000004], 1:MethodDefOrRef[06000005], 2:MethodDefOrRef[06000002] 2 == 0:TypeDef[2000005], 1:MethodDefOrRef[06000008], 2:MethodDefOrRef[06000001] 

Ensuite, vous pouvez regarder dans les tables typedef et methoddef, et vous verrez que cela se décode comme:

 in type A the method A::I2.Draw implements the method I2::Draw in type B the method B::I1.Draw implements the method I1::Draw 

La table MethodImpl montre comment la CLI représente la notion “Je dois coller dans cet emplacement quelque chose de différent de ce que choisiraient les règles de correspondance de nom habituelles”. Normalement, les règles de correspondance de noms choisiraient une méthode appelée “Draw” à insérer dans cet emplacement, et non “I1.Draw” ou “I2.Draw”.

Vous pouvez également lire la section 22.27 de la partition II de la spécification CLI.

D’après ce que je comprends, vous demandez, étant donné une sous-classe avec certaines méthodes substituées, comment savoir quelle méthode sera appelée.

En gros, il y a 3 options possibles:

  1. Si SubClass ne définit pas de méthode sur BaseClass, la méthode de BaseClass sera appelée
  2. Si SubClass OVERRIDES une méthode sur BaseClass, la méthode de la SubClass sera appelée
  3. Si SubClass définit une méthode NEW qui existe ALSO sur BaseClass, la méthode à appeler dépendra de la référence à l’object (que la variable soit typée BaseClass ou SubClass).

J’espère avoir compris votre question

edit: note rapide sur les interfaces: Si BaseClass implémente IInterface, SubClass, qui dérive de BaseClass, possède déjà l’implémentation de IInterface et n’a pas besoin de la réimplémenter. Par exemple, s’il existe un IVehicle avec une propriété MPH, et qu’il existe un BaseVehicle qui implémente que, puisque Car dérive de BaseVehicle, Car a déjà une propriété MPH.

“Table de méthode”? Non, c’est juste un contrat qui doit être satisfait. Les contrats de I1 et I2 peuvent être satisfaits avec la même méthode Draw et le seront, à moins que vous n’en sépariez un avec une implémentation implicite, comme c’est le cas ici. Sans cela, une seule méthode Draw conviendra.

Dans tous les cas, le public Draw sera appelé sauf lorsque la référence est convertie en type d’interface explicitement implémenté.

 interface I1 { void Draw(); } interface I2 { void Draw(); } class A : I1, I2 { // this is just a method in A public void Minstance() { Console.WriteLine("A::MInstance"); } // method in A, also implements I1.Draw. May be overridden in // derived types. public virtual void Draw() { Console.WriteLine("A::Draw"); } // implements I2.Draw, accessible on object a of type A via ((I2)a).Draw() void I2.Draw() { Console.WriteLine("A::I2.Draw"); } } class B : A, I1, I2 { // new method in B, does not override A.Draw, so A.Draw is only // callable on an object b of type B via ((A)b).Draw(). Types // derived from B may override this method, but can't override // A.Draw because it's hidden. Also implements I2.Draw (see notes). public new virtual void Draw() { Console.WriteLine("B::Draw"); } // implements I1.Draw, accessible on object b of type B via ((I1)b).Draw() void I1.Draw() { Console.WriteLine("B::I2.Draw"); } } 

Notes et référence: Je devais revenir à la norme (ECMA-334) pour cela, et cela se trouve au § 20.4.4 Réimplémentation d’interface:

Une classe qui hérite d’une implémentation d’interface est autorisée à réimplémenter l’interface en l’incluant dans la liste de classes de base.

Une réimplémentation d’une interface suit exactement les mêmes règles de mappage d’interface qu’une implémentation initiale d’une interface. Ainsi, le mappage d’interface hérité n’a aucun effet sur le mappage d’interface établi pour la réimplémentation de l’interface. [ Exemple : dans les déclarations

 interface IControl { void Paint(); } class Control: IControl { void IControl.Paint() {…} } class MyControl: Control, IControl { public void Paint() {} } 

le fait que Control mappe IControl.Paint sur Control.IControl.Paint n’affecte pas la réimplémentation dans MyControl , qui mappe IControl.Paint sur MyControl.Paint . fin exemple ]

Les déclarations de membre public héritées et les déclarations de membre d’interface explicite héritées participent au processus de mappage d’interface pour les interfaces réimplémentées. [ Exemple :

 interface IMethods { void F(); void G(); void H(); void I(); } class Base: IMethods { void IMethods.F() {} void IMethods.G() {} public void H() {} public void I() {} } class Derived: Base, IMethods { public void F() {} void IMethods.H() {} } 

Ici, la mise en œuvre de IMethods dans Derived mappe les méthodes d’interface sur Derived.F , Base.IMethods.G , Derived.IMethods.H et Base.I fin exemple ]

Lorsqu’une classe implémente une interface, elle implémente implicitement toutes les interfaces de base de cette interface. De même, une réimplémentation d’une interface est également implicitement une réimplémentation de toutes les interfaces de base de l’interface. [ Exemple :

 interface IBase { void F(); } interface IDerived: IBase { void G(); } class C: IDerived { void IBase.F() {…} void IDerived.G() {…} } class D: C, IDerived { public void F() {…} public void G() {…} } 

Ici, la ré-implémentation d’ IDerived ré-implémente également IBase , en mappant IBase.F sur DF . fin exemple ]

En plus d’autres réponses, je poste une réponse correcte:

  A a = new B(); a.Draw(); //A::Draw I1 i1 = new A(); i1.Draw(); //A::Draw I2 i2 = new B(); i2.Draw();// B::Draw B b = (B) a; b.Draw();// B::Draw 

Tout d’abord l’explication de nouveaux et virtuels

Nouveau

  1. new est utilisé pour créer un object, c’est-à-dire créer une instance d’une classe ou en faire un object.
  2. new est également utilisé pour (remplacer littéralement) new la méthode existante dans la classe de base.

par exemple

 A a2= new B() (creates a new object using constructor of B, because b is of type A, therefore it works ) and a2.draw() will result in execution of A class draw because the type is A. 

Virtuel

  1. Ceci établit que la méthode est virtuelle (c’est-à-dire non permanente ou physique) et doit être remplacée, c’est-à-dire que sa définition doit être fournie dans la classe sous-jacente.

passer outre

  1. Mot-clé utilisé pour indiquer que vous souhaitez remplacer cette méthode, etc.