69 votes

Pourquoi est-ce polymorphes de code C# de l'impression de ce qu'il fait?

J'ai récemment été donné le morceau de code suivant comme une sorte de puzzle pour aider à comprendre Polymorphism et Inheritance , en programmation orientée objet - C#.

// No compiling!
public class A
{
     public virtual string GetName()
     {
          return "A";
     }
 }

 public class B:A
 {
     public override string GetName()
     {
         return "B";
     }
 }

 public class C:B
 {
     public new string GetName()
     {
         return "C";
     }
 }

 void Main()
 {
     A instance = new C();
     Console.WriteLine(instance.GetName());
 }
 // No compiling!

Maintenant, après une longue, longue discussion avec l'autre développeur qui a présenté le puzzle, je sais ce que la sortie est une sortie, mais je ne vais pas gâcher pour vous. La seule question que je suis vraiment à avoir est de savoir comment nous arrivons à la sortie, de la façon dont le code suit à travers, ce qui est d'hériter de quoi, etc.

J'ai pensé C serait retourné comme cela semble être la classe qui est définie. Puis je suis allé dans ma tête, comme si B serait retourné parce que C hérite B - mais B hérite également A (qui est l'endroit où je me suis confus!).


Question:

Quelqu'un pourrait-il expliquer comment le polymorphisme et l'héritage de jouer leur rôle dans la récupération de la sortie, finalement affiché sur l'écran?

101voto

Eric Lippert Points 300275

La bonne façon de penser est d'imaginer que chaque classe exige de ses objets ont un certain nombre de "slots"; ces emplacements sont remplis avec des méthodes. À la question "quelle méthode est appelée?" vous demande de comprendre deux choses:

  1. Quel est le contenu de chaque fente?
  2. Slot est appelé?

Nous allons commencer par examiner les fentes. Il y a deux fentes. Toutes les instances d'Un sont tenus d'avoir un logement, que nous appellerons GetNameSlotA. Toutes les instances de C sont tenus d'avoir un logement, que nous appellerons GetNameSlotC. C'est ce que le "nouveau" signifie à la déclaration en C, cela veut dire "je veux une nouvelle machine à sous". Par rapport à la "remplacer" sur la déclaration en B, ce qui signifie "je ne veux pas d'une nouvelle machine à sous, je veux re-utiliser GetNameSlotA".

Bien sûr, C hérite de A, alors C doit également avoir un logement GetNameSlotA. Par conséquent, les instances de C ont deux fentes -- GetNameSlotA, et GetNameSlotC. Les Instances de A ou de B qui ne sont pas C ont un logement, GetNameSlotA.

Maintenant, ce qui se passe dans ces deux fentes lorsque vous créez un nouveau C? Il existe trois méthodes, que nous appellerons GetNameA, GetNameB, et GetNameC.

La déclaration d'Un dit "mettre GetNameA dans GetNameSlotA". A est une super-classe de C, si Une règle s'applique à C.

La déclaration de B dit "mettre GetNameB dans GetNameSlotA". B est une super-classe de C, donc B est la règle s'applique dans les cas de C. Maintenant, nous avons un conflit entre A et B. B est la plus dérivée, donc il gagne -- B de la règle remplace Une règle. Par conséquent, le mot "remplacer" dans la déclaration.

La déclaration de C dit "mettre GetNameC dans GetNameSlotC".

Par conséquent, votre nouveau C aura deux fentes. GetNameSlotA contiendra GetNameB et GetNameSlotC contiendra GetNameC.

Nous avons maintenant déterminé quelles sont les méthodes en ce que des fentes, de sorte que nous avons répondu à notre première question.

Maintenant, nous devons répondre à la seconde question. Ce slot est appelé?

Pensez que vous êtes le compilateur. Vous disposez d'une variable. Tout ce que vous savez à ce sujet est qu'il est de type A. Vous êtes invité à résoudre un appel de méthode sur cette variable. Vous regardez les machines à sous disponibles sur l'Un, et la seule fente que vous pouvez trouver qui correspond à est GetNameSlotA. Vous ne savez pas à propos de GetNameSlotC, car vous avez seulement une variable de type A; pourquoi voudriez-vous regarder pour les logements qui ne s'appliquent qu'à C?

Donc ceci est un appel à tout ce qui est en GetNameSlotA. Nous avons déjà déterminé que, au moment de l'exécution, GetNameB sera dans cet emplacement. Donc, ceci est un appel à GetNameB.

L'emporter clé ici est que en C# résolution de surcharge choisit une fente et génère un appel à tout ce qui se trouve dans cet emplacement.

24voto

Blindy Points 26706

Il devrait revenir "B", car B.GetName() est tenue dans la petite table virtuelle de la boîte pour l' A.GetName() fonction. C.GetName() est un moment de la compilation de "substitution", il ne modifie pas la table virtuelle de sorte que vous ne pourrez pas le récupérer par le biais d'un pointeur à l' A.

3voto

Thomas Weller Points 8428

Facile, il vous suffit de garder l'arbre d'héritage à l'esprit.

Dans votre code, vous contenir une référence à une classe de type "A", qui est instancié par une instance de type "C". Maintenant, pour résoudre la méthode exacte de l'adresse de l'virtuel "GetName () "méthode", le compilateur va jusqu'à la hiérarchie d'héritage et de la recherche la plus récente remplacer (à noter que seule "virtuel" est un "override", "nouveau" est quelque chose de complètement différent...).

C'est en bref ce qui se passe. Le nouveau mot clé de type " C "ne jouent un rôle si vous l'appelleriez sur une instance de type" C " et le compilateur puis irait à l'encontre de tous les possibles de l'héritage des relations tout à fait. Strictement parlé, cela n'a rien à voir avec le polymorphisme, vous pouvez voir que le fait que si vous avez un masque virtuel ou non de méthode virtuelle avec le "nouveau" mot-clé ne fait aucune différence...

Les "nouveaux" dans la classe " C "veut dire exactement: Si vous appelez le" GetName()' sur une instance de cette (exacte) du type, puis tout oublier et à l'utilisation de CETTE méthode. "Virtuelle", contrairement signifie: Aller jusqu'à l'arbre d'héritage jusqu'à ce que vous trouver une méthode avec ce nom, quel que soit le type exact de l'appel, l'instance de l'.

2voto

Mange Points 1

OK, le post est un peu vieux, mais c'est une excellente question et une excellente réponse, alors je voulais juste ajouter mes pensées.

Prenons l'exemple suivant, qui est le même qu'avant, sauf pour la fonction principale:

// No compiling!
public class A
{
    public virtual string GetName()
    {
        return "A";
    }
}

public class B:A
{
    public override string GetName()
    {
        return "B";
    }
}

public class C:B
{
    public new string GetName()
    {
        return "C";
    }
}

void Main()
{
    Console.Write ( "Type a or c: " );
    string input = Console.ReadLine();

    A instance = null;
    if      ( input == "a" )   instance = new A();
    else if ( input == "c" )   instance = new C();

   Console.WriteLine( instance.GetName() );
}
// No compiling!

Maintenant, il est vraiment évident que l'appel de fonction ne peut pas être liée à une fonction particulière, au moment de la compilation. Quelque chose doit être compilé cependant, et que l'information ne peut dépendre du type de la référence. Alors, il serait impossible l'exécution de la GetName fonction de classe C avec toute référence autre que celui de type C.

P. S. je devrais Peut-être ai utilisé le terme de méthode à la place de la fonction, mais comme l'a dit Shakespeare: Une fonction par un autre nom qui est encore une fonction :)

-1voto

FractalizeR Points 12887

En fait, je pense qu'il devrait afficher C, parce que nouvel opérateur vient se cache tous ancêtre méthodes avec le même nom. Donc, avec les méthodes A et B, caché, seulement C reste visible.

http://msdn.microsoft.com/en-us/library/51y09td4%28VS.71%29.aspx#vclrfnew%5Fnewmodifier

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X