Pourquoi l'appel d'une méthode dans ma classe dérivée appelle-t-il la méthode de classe de base?

146

Considérez ce code:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Teacher();
        person.ShowInfo();
        Console.ReadLine();
    }
}

public class Person
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public new void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Lorsque j'exécute ce code, ce qui suit est généré:

Je suis personne

Cependant, vous pouvez voir qu'il s'agit d'une instance de Teacher, pas de Person. Pourquoi le code fait-il cela?

bleuâtre
la source
3
Question d'une personne Java: est la Console.ReadLine (); nécessaire pour cet exemple?
Rich
2
@Shahrooz Je ne peux pas répondre à votre question - je ne connais pas C #. Je posais une question C # très triviale, à savoir si l'appel à ReadLine dans la méthode principale est nécessaire pour pouvoir appeler WriteLine dans les classes Person et Teacher.
Rich
6
Oui, .Net ferme automatiquement la fenêtre de la console lorsque Main () quitte. pour contourner cela, nous utilisons Console.Read () ou Console.Readline () pour attendre une entrée supplémentaire afin que la console reste ouverte.
Captain Kenpachi
15
@ Rich non, ce n'est pas nécessaire , mais vous le verrez souvent pour cette raison: lors de l'exécution d'un programme console à partir de Visual Studio, à la fin du programme, la fenêtre de commande se ferme immédiatement, donc si vous voulez voir la sortie du programme, vous devez le dire attendre.
AakashM
1
@AakashM Merci - Je passe mon temps dans Eclipse où la console fait partie de la fenêtre Eclipse et ne se ferme donc pas. Cela est parfaitement logique.
Rich

Réponses:

368

Il y a une différence entre newet virtual/ override.

Vous pouvez imaginer qu'une classe, une fois instanciée, n'est rien de plus qu'une table de pointeurs, pointant vers l'implémentation réelle de ses méthodes. L'image suivante devrait bien visualiser cela:

Illustration des implémentations de méthodes

Maintenant, il existe différentes manières, une méthode peut être définie. Chacun se comporte différemment lorsqu'il est utilisé avec l'héritage. La méthode standard fonctionne toujours comme l'illustre l'image ci-dessus. Si vous souhaitez modifier ce comportement, vous pouvez associer différents mots-clés à votre méthode.

1. Classes abstraites

Le premier est abstract. abstractles méthodes ne pointent simplement nulle part:

Illustration de classes abstraites

Si votre classe contient des membres abstraits, elle doit également être marquée comme abstract, sinon le compilateur ne compilera pas votre application. Vous ne pouvez pas créer d'instances de abstractclasses, mais vous pouvez en hériter et créer des instances de vos classes héritées et y accéder à l'aide de la définition de classe de base. Dans votre exemple, cela ressemblerait à:

public abstract class Person
{
    public abstract void ShowInfo();
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

public class Student : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a student!");
    }
}

S'il est appelé, le comportement de ShowInfovarie en fonction de l'implémentation:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

Les deux, Students et Teachers sont des Persons, mais ils se comportent différemment quand on leur demande de demander des informations sur eux-mêmes. Cependant, la façon de leur demander de demander leurs informations est la même: en utilisant l' Personinterface de classe.

Alors que se passe-t-il dans les coulisses, lorsque vous héritez Person? Lors de l'implémentation ShowInfo, le pointeur ne pointe plus vers nulle part , il pointe désormais vers l'implémentation réelle! Lors de la création d'une Studentinstance, il pointe vers Students ShowInfo:

Illustration des méthodes héritées

2. Méthodes virtuelles

La deuxième façon est d'utiliser des virtualméthodes. Le comportement est le même, sauf que vous fournissez une implémentation par défaut facultative dans votre classe de base. Les classes avec des virtualmembres peuvent être instanciées, mais les classes héritées peuvent fournir différentes implémentations. Voici à quoi votre code devrait ressembler pour fonctionner:

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am a person!");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

La principale différence est que le membre de base Person.ShowInfone pointe plus vers nulle part . C'est également la raison pour laquelle vous pouvez créer des instances de Person(et il n'est donc pas nécessaire de le marquer abstractplus longtemps):

Illustration d'un membre virtuel dans une classe de base

Vous devriez remarquer que cela ne semble pas différent de la première image pour le moment. C'est parce que la virtualméthode pointe vers une implémentation «à la manière standard ». En utilisant virtual, vous pouvez dire Personsqu'ils peuvent (pas obligés ) fournir une implémentation différente pour ShowInfo. Si vous fournissez une implémentation différente (en utilisant override), comme je l'ai fait pour ce qui Teacherprécède, l'image aura la même apparence que pour abstract. Imaginez, nous n'avons pas fourni d'implémentation personnalisée pour Students:

public class Student : Person
{
}

Le code serait appelé comme ceci:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

Et l'image de Studentressemblerait à ceci:

Illustration de l'implémentation par défaut d'une méthode, à l'aide du mot-clé virtuel

3. Le mot-clé magique `new` aka" Shadowing "

newest plus un hack autour de cela. Vous pouvez fournir des méthodes dans des classes généralisées, qui ont les mêmes noms que des méthodes dans la classe / interface de base. Les deux pointent vers leur propre implémentation personnalisée:

Illustration du "chemin" à l'aide du mot-clé new

L'implémentation ressemble à celle que vous avez fournie. Le comportement diffère selon la manière dont vous accédez à la méthode:

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

Ce comportement peut être souhaité, mais dans votre cas, il est trompeur.

J'espère que cela rend les choses plus claires à comprendre pour vous!

Carsten
la source
9
Merci pour votre excellente réponse
6
Qu'avez-vous utilisé pour générer ces diagrammes?
BlueRaja - Danny Pflughoeft
2
Réponse excellente et très approfondie.
Nik Bougalis
8
tl; dr que vous avez utilisé, newce qui rompt l'héritage de la fonction et sépare la nouvelle fonction de la fonction de la superclasse
ratchet freak
3
@Taymon: En fait non ... Je voulais juste préciser que l'appel va maintenant contre Person, pas Student;)
Carsten
45

Le polymorphisme des sous-types en C # utilise la virtualité explicite, similaire à C ++ mais contrairement à Java. Cela signifie que vous devez explicitement marquer les méthodes comme remplaçables (ie virtual). En C #, vous devez également marquer explicitement les méthodes de substitution comme remplaçant (c'est override-à- dire ) pour éviter les fautes de frappe.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Dans le code de votre question, vous utilisez new, qui effectue l' observation au lieu de remplacer. L'observation affecte simplement la sémantique à la compilation plutôt que la sémantique à l'exécution, d'où la sortie involontaire.

Communauté
la source
4
Qui peut dire que le PO sait ce que cela signifie.
Cole Johnson
@ColeJohnson Je vais ajouter une clarification.
25

Vous devez rendre la méthode virtuelle et vous devez remplacer la fonction dans la classe enfant, afin d'appeler la méthode de l'objet de classe que vous avez mis dans la référence de classe parent.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Méthodes virtuelles

Lorsqu'une méthode virtuelle est appelée, le type d'exécution de l'objet est vérifié pour un membre de substitution. Le membre de substitution dans la classe la plus dérivée est appelé, qui peut être le membre d'origine, si aucune classe dérivée n'a remplacé le membre. Par défaut, les méthodes ne sont pas virtuelles. Vous ne pouvez pas remplacer une méthode non virtuelle. Vous ne pouvez pas utiliser le modificateur virtuel avec les modificateurs statiques, abstraits, privés ou de remplacement, MSDN .

Utiliser New pour l'observation

Vous utilisez un nouveau mot clé au lieu de remplacer, c'est ce que fait nouveau

  • Si la méthode de la classe dérivée n'est pas précédée de mots-clés nouveaux ou remplacés, le compilateur émettra un avertissement et la méthode se comportera comme si le nouveau mot-clé était présent.

  • Si la méthode de la classe dérivée est précédée du nouveau mot clé, la méthode est définie comme étant indépendante de la méthode de la classe de base . Cet article MSDN l' explique très bien.

Reliure anticipée VS Reliure tardive

Nous avons une liaison précoce au moment de la compilation pour la méthode normale (non virtuelle) qui est le cas courant, le compilateur liera l'appel à la méthode de la classe de base qui est la méthode de type référence (classe de base) au lieu de l'objet est tenu dans le référentiel de base class ie objet de classe dérivé . C'est parce que ce ShowInfon'est pas une méthode virtuelle. La liaison tardive est effectuée au moment de l'exécution pour (méthode virtuelle / remplacée) à l'aide de la table de méthodes virtuelles (vtable).

Pour une fonction normale, le compilateur peut déterminer son emplacement numérique en mémoire. Ensuite, lorsque la fonction est appelée, il peut générer une instruction pour appeler la fonction à cette adresse.

Pour un objet qui a des méthodes virtuelles, le compilateur générera une v-table. Il s'agit essentiellement d'un tableau contenant les adresses des méthodes virtuelles. Chaque objet qui a une méthode virtuelle contiendra un membre caché généré par le compilateur qui est l'adresse de la v-table. Lorsqu'une fonction virtuelle est appelée, le compilateur déterminera la position de la méthode appropriée dans la v-table. Il générera ensuite du code pour regarder dans la v-table des objets et appeler la méthode virtuelle à cette position, Reference .

Adil
la source
7

Je veux m'appuyer sur la réponse d' Achratt . Par souci d'exhaustivité, la différence est que l'OP s'attend à ce que le newmot - clé dans la méthode de la classe dérivée remplace la méthode de la classe de base. Ce qu'il fait réellement, c'est masquer la méthode de la classe de base.

En C #, comme une autre réponse l'a mentionné, la substitution de méthode traditionnelle doit être explicite; la méthode de classe de base doit être marquée comme virtualet la classe dérivée doit spécifiquement overridela méthode de classe de base. Si cela est fait, peu importe que l'objet soit traité comme une instance de la classe de base ou de la classe dérivée; la méthode dérivée est trouvée et appelée. Cela se fait de la même manière qu'en C ++; une méthode marquée «virtuelle» ou «override», lorsqu'elle est compilée, est résolue «tardivement» (à l'exécution) en déterminant le type réel de l'objet référencé et en parcourant la hiérarchie d'objets vers le bas le long de l'arborescence du type de variable au type d'objet réel, pour trouver l'implémentation la plus dérivée de la méthode définie par le type de variable.

Cela diffère de Java, qui autorise les «remplacements implicites»; par exemple les méthodes (non statiques), la simple définition d'une méthode de la même signature (nom et nombre / type de paramètres) entraînera la sous-classe à remplacer la superclasse.

Comme il est souvent utile d'étendre ou de remplacer la fonctionnalité d'une méthode non virtuelle que vous ne contrôlez pas, C # inclut également le newmot-clé contextuel. Le newmot-clé "masque" la méthode parente au lieu de la remplacer. Toute méthode héritable peut être masquée, qu'elle soit virtuelle ou non; cela vous permet, le développeur, d'exploiter les membres que vous souhaitez hériter d'un parent, sans avoir à contourner ceux que vous ne faites pas, tout en vous permettant de présenter la même «interface» aux consommateurs de votre code.

Le masquage fonctionne de la même manière que le remplacement du point de vue d'une personne utilisant votre objet au niveau ou en dessous du niveau d'héritage auquel la méthode de masquage est définie. À partir de l'exemple de la question, un codeur créant un enseignant et stockant cette référence dans une variable de type Teacher verra le comportement de l'implémentation ShowInfo () de Teacher, qui masque celle de Person. Cependant, quelqu'un qui travaille avec votre objet dans une collection d'enregistrements Person (comme vous) verra le comportement de l'implémentation Person de ShowInfo (); parce que la méthode de Teacher ne remplace pas son parent (ce qui exigerait également que Person.ShowInfo () soit virtuelle), le code fonctionnant au niveau d'abstraction de Person ne trouvera pas l'implémentation de Teacher et ne l'utilisera pas.

De plus, non seulement le newmot-clé le fera explicitement, mais C # permet le masquage implicite de méthode; définir simplement une méthode avec la même signature qu'une méthode de classe parent, sans overrideou new, la masquera (bien que cela produise un avertissement du compilateur ou une plainte de certains assistants de refactoring comme ReSharper ou CodeRush). C'est le compromis que les concepteurs de C # ont trouvé entre les remplacements explicites de C ++ et ceux implicites de Java, et bien qu'il soit élégant, il ne produit pas toujours le comportement auquel vous vous attendez si vous venez d'un arrière-plan dans l'un des langages plus anciens.

Voici les nouveautés: cela devient complexe lorsque vous combinez les deux mots-clés dans une longue chaîne d'héritage. Considérer ce qui suit:

class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }

Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();

foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();

Console.WriteLine("---");

Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();

foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();    

Console.WriteLine("---");

Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;

foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();    
bat3.DoFoo();

Production:

Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat

La première série de cinq est à prévoir; car chaque niveau a une implémentation et est référencé en tant qu'objet du même type que celui qui a été instancié, le runtime résout chaque appel au niveau d'héritage référencé par le type de variable.

Le deuxième ensemble de cinq est le résultat de l'affectation de chaque instance à une variable du type parent immédiat. Maintenant, certaines différences de comportement éclatent; foo2, qui est en fait une Barconversion en a Foo, trouvera toujours la méthode la plus dérivée du type d'objet réel Bar. bar2est un Baz, mais contrairement à foo2, parce que Baz ne remplace pas explicitement l'implémentation de Bar (il ne peut pas; Bar sealedit), il n'est pas vu par le runtime en regardant "de haut en bas", donc l'implémentation de Bar est appelée à la place. Notez que Baz n'a pas besoin d'utiliser le newmot - clé; vous recevrez un avertissement du compilateur si vous omettez le mot-clé, mais le comportement implicite en C # est de masquer la méthode parente. baz2est un Bai, qui remplace Bazl »newimplémentation, donc son comportement est similaire à celui de foo2's; l'implémentation du type d'objet réel dans Bai est appelée. bai2est a Bat, qui cache encore une fois l' Baiimplémentation de la méthode de son parent , et il se comporte de la même manière que bar2même si l'implémentation de Bai n'est pas scellée, donc théoriquement Bat aurait pu remplacer au lieu de masquer la méthode. Enfin, bat2est a Bak, qui n'a aucune implémentation de substitution de l'un ou l'autre genre, et utilise simplement celle de son parent.

Le troisième ensemble de cinq illustre le comportement de résolution descendante complète. Tout fait en fait référence à une instance de la classe la plus dérivée de la chaîne, Bakmais la résolution à chaque niveau de type de variable est effectuée en commençant à ce niveau de la chaîne d'héritage et en descendant jusqu'au remplacement explicite le plus dérivé de la méthode, qui sont ceux Bar, Baiet Bat. La méthode masquant ainsi "brise" la chaîne d'héritage primordiale; vous devez travailler avec l'objet au niveau ou en dessous du niveau d'héritage qui cache la méthode pour que la méthode de masquage soit utilisée. Sinon, la méthode cachée est "découverte" et utilisée à la place.

KeithS
la source
4

Veuillez lire sur le polymorphisme en C #: Polymorphisme (Guide de programmation C #)

Voici un exemple à partir de là:

Lorsque le nouveau mot-clé est utilisé, les nouveaux membres de classe sont appelés à la place des membres de classe de base qui ont été remplacés. Ces membres de classe de base sont appelés membres masqués. Les membres de classe masqués peuvent toujours être appelés si une instance de la classe dérivée est convertie en une instance de la classe de base. Par exemple:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.
Emil
la source
3

Vous devez le créer virtual, puis remplacer cette fonction dans Teacher. Lorsque vous héritez et utilisez le pointeur de base pour faire référence à une classe dérivée, vous devez la remplacer en utilisant virtual. newsert à masquer la baseméthode de classe sur une référence de classe dérivée et non sur une baseréférence de classe.

Jay Patel
la source
3

Je voudrais ajouter quelques exemples supplémentaires pour développer les informations à ce sujet. J'espère que cela aide aussi:

Voici un exemple de code qui clarifie ce qui se passe lorsqu'un type dérivé est affecté à un type de base. Quelles méthodes sont disponibles et la différence entre les méthodes remplacées et masquées dans ce contexte.

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


    class A
    {
        public virtual void foo()
        {
            Console.WriteLine("A.foo()");
        }

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

    class C : B
    {
        public override void foo()
        {
            Console.WriteLine("C.foo()");
        }

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

Une autre petite anomalie est que, pour la ligne de code suivante:

A a = new B();    
a.foo(); 

Le compilateur VS (intellisense) afficherait a.foo () comme A.foo ().

Par conséquent, il est clair que lorsqu'un type plus dérivé est affecté à un type de base, la variable «type de base» agit comme le type de base jusqu'à ce qu'une méthode qui est remplacée dans un type dérivé soit référencée. Cela peut devenir un peu contre-intuitif avec des méthodes cachées ou des méthodes portant le même nom (mais non remplacées) entre les types parent et enfant.

Cet exemple de code devrait aider à délimiter ces mises en garde!

Vaibhav
la source
2

C # est différent de java dans le comportement de substitution de classe parent / enfant. Par défaut, en Java, toutes les méthodes sont virtuelles, de sorte que le comportement souhaité est pris en charge par défaut.

En C #, vous devez marquer une méthode comme virtuelle dans la classe de base, puis vous obtiendrez ce que vous voulez.

Adrian Salazar
la source
2

Le nouveau mot-clé indique que la méthode de la classe actuelle ne fonctionnera que si vous avez une instance de la classe Teacher stockée dans une variable de type Teacher. Ou vous pouvez le déclencher en utilisant des castings: ((Teacher) Person) .ShowInfo ()

Miguel
la source
1

Le type de variable «enseignant» ici est typeof(Person)et ce type ne sait rien de la classe Enseignant et n'essaye pas de rechercher des méthodes dans les types dérivés. Pour la méthode de classe Maître appel , vous devez jeter votre variable: (person as Teacher).ShowInfo().

Pour appeler une méthode spécifique basée sur le type de valeur, vous devez utiliser le mot clé «virtual» dans votre classe de base et remplacer les méthodes virtuelles dans les classes dérivées. Cette approche permet d'implémenter des classes dérivées avec ou sans remplacement des méthodes virtuelles. Les méthodes de la classe de base seront appelées pour les types sans virtuels surchargés.

public class Program
{
    private static void Main(string[] args)
    {
        Person teacher = new Teacher();
        teacher.ShowInfo();

        Person incognito = new IncognitoPerson ();
        incognito.ShowInfo();

        Console.ReadLine();
    }
}

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

public class IncognitoPerson : Person
{

}
Vi Ki
la source
1

Peut-être trop tard ... Mais la question est simple et la réponse doit avoir le même niveau de complexité.

Dans votre variable de code, la personne ne sait rien de Teacher.ShowInfo (). Il n'y a aucun moyen d'appeler la dernière méthode à partir d'une référence de classe de base, car ce n'est pas virtuel.

Il existe une approche utile de l'héritage - essayez d'imaginer ce que vous voulez dire avec votre hiérarchie de code. Essayez également d'imaginer ce que dit un outil ou un autre sur lui-même. Par exemple, si vous ajoutez une fonction virtuelle dans une classe de base, vous supposez: 1. qu'elle peut avoir une implémentation par défaut; 2. il pourrait être réimplémenté dans la classe dérivée. Si vous ajoutez une fonction abstraite, cela signifie qu'une seule chose - la sous-classe doit créer une implémentation. Mais dans le cas où vous avez une fonction simple, vous ne vous attendez pas à ce que quiconque modifie sa mise en œuvre.

Illia Levandovskyi
la source
0

Le compilateur fait cela car il ne sait pas qu'il s'agit d'un fichier Teacher. Tout ce qu'il sait, c'est que c'est un Personou quelque chose qui en est dérivé. Donc, tout ce qu'il peut faire est d'appeler la Person.ShowInfo()méthode.

Cole Johnson
la source
0

Je voulais juste donner une brève réponse -

Vous devez utiliser virtualet overridedans les classes qui pourraient être remplacées. À utiliser virtualpour les méthodes qui peuvent être remplacées par des classes enfants et à utiliser overridepour les méthodes qui doivent remplacer ces virtualméthodes.

Shruti Kapoor
la source
0

J'ai écrit le même code que vous avez mentionné ci-dessus en java, à l'exception de quelques changements et cela a bien fonctionné comme sauf. La méthode de la classe de base est remplacée et la sortie affichée est "Je suis enseignant".

Raison: Comme nous créons une référence de la classe de base (qui est capable d'avoir une instance de référence de la classe dérivée) qui contient en fait la référence de la classe dérivée. Et comme nous savons que l'instance regarde toujours d'abord ses méthodes si elle la trouve là, elle l'exécute, et si elle n'y trouve pas la définition, elle monte dans la hiérarchie.

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}
Sanjeev Chauhan
la source
0

S'appuyant sur l'excellente démonstration de Keith S. et les réponses de qualité de tous les autres et dans un souci d'exhaustivité, allons de l'avant et jetons des implémentations d'interface explicites pour montrer comment cela fonctionne. Considérez ce qui suit:

namespace LinqConsoleApp {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

}

Voici le résultat:

personne: Je suis Person == LinqConsoleApp.Teacher

enseignant: Je suis enseignant == LinqConsoleApp.Teacher

person1: Je suis enseignant == LinqConsoleApp.Teacher

person2: Je suis enseignant == LinqConsoleApp.Teacher

enseignant1: Je suis enseignant == LinqConsoleApp.Teacher

person4: Je suis Person == LinqConsoleApp.Person

person3: Je suis l'interface Person == LinqConsoleApp.Person

Deux choses à noter: la
méthode Teacher.ShowInfo () omet le mot-clé new. Lorsque new est omis, le comportement de la méthode est le même que si le nouveau mot-clé était explicitement défini.

Vous ne pouvez utiliser le mot clé override qu'en conjonction avec le mot clé virtuel. La méthode de classe de base doit être virtuelle. Ou abstraite, auquel cas la classe doit également être abstraite.

person obtient l'implémentation de base de ShowInfo car la classe Teacher ne peut pas remplacer l'implémentation de base (pas de déclaration virtuelle) et person est .GetType (Teacher) donc elle masque l'implémentation de la classe Teacher.

Teacher obtient l'implémentation Teacher dérivée de ShowInfo parce que Teacher est de Typeof (Teacher) et n'est pas au niveau d'héritage Person.

person1 obtient l'implémentation de Teacher dérivée car il s'agit de .GetType (Teacher) et le nouveau mot-clé implicite masque l'implémentation de base.

person2 obtient également l'implémentation de Teacher dérivée même si elle implémente IPerson et il obtient un cast explicite en IPerson. C'est encore une fois parce que la classe Teacher n'implémente pas explicitement la méthode IPerson.ShowInfo ().

teacher1 obtient également l'implémentation de Teacher dérivée car il s'agit de .GetType (Teacher).

Seule person3 obtient l'implémentation IPerson de ShowInfo car seule la classe Person implémente explicitement la méthode et person3 est une instance du type IPerson.

Afin d'implémenter explicitement une interface, vous devez déclarer une instance var du type d'interface cible et une classe doit implémenter explicitement (entièrement qualifier) ​​le (s) membre (s) de l'interface.

Notez que même personne4 n'obtient l'implémentation IPerson.ShowInfo. En effet, même si person4 est .GetType (Person) et même si Person implémente IPerson, person4 n'est pas une instance de IPerson.

acier
la source
Je vois que le formatage du code est un peu un défi. Pas le temps de faire le joli pour le moment ...
steely
0

Exemple LinQPad pour lancer aveuglément et réduire la duplication de code Ce que je pense est ce que vous essayiez de faire.

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
Yrd
la source