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.
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.
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:
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"); } }
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
mappeIControl.Paint
surControl.IControl.Paint
n’affecte pas la réimplémentation dansMyControl
, qui mappeIControl.Paint
surMyControl.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
dansDerived
mappe les méthodes d’interface surDerived.F
,Base.IMethods.G
,Derived.IMethods.H
etBase.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 égalementIBase
, en mappantIBase.F
surDF
. 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
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
passer outre