web-dev-qa-db-fra.com

Marquage explicite de la classe dérivée en tant qu'interface d'implémentation de la classe de base

interface IBase
{
    string Name { get; }
}

class Base : IBase
{
    public Base() => this.Name = "Base";
    public string Name { get; }
}

class Derived : Base//, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}


class Program
{
    static void Main(string[] args)
    {
        IBase o = new Derived();
        Console.WriteLine(o.Name);
    }
}

Dans ce cas, la sortie sera "Base".

Si je déclare explicitement que Derived implémente IBase (qui est en fait déjà implémenté par la classe de base Base et que cette annotation semble inutile), le résultat sera "Derived"

class Derived : Base, IBase
{
    public Derived() => this.Name = "Derived";
    public new string Name { get; }
}

Quelle est la raison d'un tel comportement?

VS 15.3.5, C # 7

16
yevgenijz

Cela est expliqué dans les sections 13.4.4 à 13.4.6 de la spécification C # 5. Les sections pertinentes sont citées ci-dessous, mais si vous indiquez explicitement qu'une classe implémente une interface, elle déclenche à nouveau le mappage d'interface. Le compilateur prend donc la classe that pour déterminer quelle implémentation chaque membre d'interface est. orienté vers.

13.4.4 Mappage d'interface

Une classe ou une structure doit fournir les implémentations de tous les membres des interfaces répertoriées dans la liste de classes de base de la classe ou de la structure. Le processus de localisation des implémentations des membres d'interface dans une classe ou une structure d'implémentation est appelé mappage d'interface.

Le mappage d'interface pour une classe ou une structure C localise une implémentation pour chaque membre de chaque interface spécifiée dans la liste de classes de base de C. L'implémentation d'un membre d'interface particulier I.M, où I est l'interface dans laquelle le membre M est déclaré, est déterminée en examinant chaque classe ou struct S, en commençant par C et en répétant pour chaque classe de base successive de C, jusqu'à ce qu'une correspondance soit trouvée. :

  • Si S contient une déclaration d'une implémentation de membre d'interface explicite qui correspond à I et M, alors ce membre est l'implémentation de I.M.
  • Sinon, si S contient une déclaration d'un membre public non statique correspondant à M, alors ce membre est l'implémentation de I.M. Si plusieurs membres correspondent, le membre correspondant à l'implémentation de I.M n'est pas spécifié. Cette situation ne peut se produire que si S est un type construit dans lequel les deux membres déclarés dans le type générique ont des signatures différentes, mais les arguments de type rendent leurs signatures identiques.

...

13.4.5 Héritage d'implémentation d'interface

Une classe hérite de toutes les implémentations d'interface fournies par ses classes de base . Sans réimplémenter explicitement une interface, une classe dérivée ne peut en aucun cas modifier les mappages d'interface hérités de ses classes de base. Par exemple, dans les déclarations

interface IControl
{
    void Paint();
}
class Control: IControl
{
    public void Paint() {...}
}
class TextBox: Control
{
    new public void Paint() {...}
}

la méthode Paint dans TextBox masque la méthode Paint dans Control, mais ne modifie pas le mappage de Control.Paint sur IControl.Paint, et les appels à Paint par le biais d'instances de classe et d'interface auront les effets suivants

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();            // invokes Control.Paint();
t.Paint();            // invokes TextBox.Paint();
ic.Paint();           // invokes Control.Paint();
it.Paint();           // invokes Control.Paint();

...

13.4.6 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. Par 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 nouvelle implémentation dans MyControl, qui mappe IControl.Paint sur MyControl.Paint.

17
Jon Skeet

Si Derived n'implémente pas IBase et déclare new string Name, cela signifie que Derived.Name et IBase.Name ne sont pas identiques logiquement. Ainsi, lorsque vous accédez à IBase.Name, il le recherche dans la classe Base, en implémentant IBase. Si vous supprimez la propriété new string Name, la sortie sera Derived, car maintenant Derived.Name = Base.Name = IBase.Name. Si vous implémentez explicitement IBase, le résultat sera Derived, car Derived.Name = IBase.Name. Si vous transformez o en Derived, le résultat sera Derived, car vous accédez maintenant à Derived.Name au lieu de IBase.Name.

1
Dmitry Pavlushin