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?
c#
class
derived-class
bleuâtre
la source
la source
Réponses:
Il y a une différence entre
new
etvirtual
/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:
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
.abstract
les méthodes ne pointent simplement nulle part: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 deabstract
classes, 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 à:S'il est appelé, le comportement de
ShowInfo
varie en fonction de l'implémentation:Les deux,
Student
s etTeacher
s sont desPerson
s, 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'Person
interface de classe.Alors que se passe-t-il dans les coulisses, lorsque vous héritez
Person
? Lors de l'implémentationShowInfo
, le pointeur ne pointe plus vers nulle part , il pointe désormais vers l'implémentation réelle! Lors de la création d'uneStudent
instance, il pointe versStudent
sShowInfo
:2. Méthodes virtuelles
La deuxième façon est d'utiliser des
virtual
mé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 desvirtual
membres peuvent être instanciées, mais les classes héritées peuvent fournir différentes implémentations. Voici à quoi votre code devrait ressembler pour fonctionner:La principale différence est que le membre de base
Person.ShowInfo
ne pointe plus vers nulle part . C'est également la raison pour laquelle vous pouvez créer des instances dePerson
(et il n'est donc pas nécessaire de le marquerabstract
plus longtemps):Vous devriez remarquer que cela ne semble pas différent de la première image pour le moment. C'est parce que la
virtual
méthode pointe vers une implémentation «à la manière standard ». En utilisantvirtual
, vous pouvez direPersons
qu'ils peuvent (pas obligés ) fournir une implémentation différente pourShowInfo
. Si vous fournissez une implémentation différente (en utilisantoverride
), comme je l'ai fait pour ce quiTeacher
précède, l'image aura la même apparence que pourabstract
. Imaginez, nous n'avons pas fourni d'implémentation personnalisée pourStudent
s:Le code serait appelé comme ceci:
Et l'image de
Student
ressemblerait à ceci:3. Le mot-clé magique `new` aka" Shadowing "
new
est 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:L'implémentation ressemble à celle que vous avez fournie. Le comportement diffère selon la manière dont vous accédez à la méthode:
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!
la source
new
ce qui rompt l'héritage de la fonction et sépare la nouvelle fonction de la fonction de la superclassePerson
, pasStudent
;)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'estoverride
-à- dire ) pour éviter les fautes de frappe.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.la source
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.
Méthodes virtuelles
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
ShowInfo
n'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).la source
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
new
mot - 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
virtual
et la classe dérivée doit spécifiquementoverride
la 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
new
mot-clé contextuel. Lenew
mot-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
new
mot-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, sansoverride
ounew
, 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:
Production:
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 uneBar
conversion en aFoo
, trouvera toujours la méthode la plus dérivée du type d'objet réel Bar.bar2
est unBaz
, mais contrairement àfoo2
, parce que Baz ne remplace pas explicitement l'implémentation de Bar (il ne peut pas; Barsealed
it), 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 lenew
mot - 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.baz2
est unBai
, qui remplaceBaz
l »new
implémentation, donc son comportement est similaire à celui defoo2
's; l'implémentation du type d'objet réel dans Bai est appelée.bai2
est aBat
, qui cache encore une fois l'Bai
implémentation de la méthode de son parent , et il se comporte de la même manière quebar2
mê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,bat2
est aBak
, 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,
Bak
mais 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 ceuxBar
,Bai
etBat
. 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.la source
Veuillez lire sur le polymorphisme en C #: Polymorphisme (Guide de programmation C #)
Voici un exemple à partir de là:
la source
Vous devez le créer
virtual
, puis remplacer cette fonction dansTeacher
. 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 utilisantvirtual
.new
sert à masquer labase
méthode de classe sur une référence de classe dérivée et non sur unebase
référence de classe.la source
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.
Une autre petite anomalie est que, pour la ligne de code suivante:
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!
la source
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.
la source
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 ()
la source
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.
la source
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.
la source
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 unPerson
ou quelque chose qui en est dérivé. Donc, tout ce qu'il peut faire est d'appeler laPerson.ShowInfo()
méthode.la source
Je voulais juste donner une brève réponse -
Vous devez utiliser
virtual
etoverride
dans les classes qui pourraient être remplacées. À utiliservirtual
pour les méthodes qui peuvent être remplacées par des classes enfants et à utiliseroverride
pour les méthodes qui doivent remplacer cesvirtual
méthodes.la source
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.
la source
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 {
}
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.
la source
Exemple LinQPad pour lancer aveuglément et réduire la duplication de code Ce que je pense est ce que vous essayiez de faire.
la source