Quelles sont les différences entre la déclaration d'une méthode dans un type de base "virtual
" et son remplacement dans un type enfant à l'aide du mot clé "override
" par opposition à l'utilisation simple du mot clé "new
" pour déclarer la méthode correspondante dans le type enfant?
Le mot-clé "nouveau" ne remplace pas, il signifie une nouvelle méthode qui n'a rien à voir avec la méthode de la classe de base.
public class Foo
{
public bool DoSomething() { return false; }
}
public class Bar : Foo
{
public new bool DoSomething() { return true; }
}
public class Test
{
public static void Main ()
{
Foo test = new Bar ();
Console.WriteLine (test.DoSomething ());
}
}
Ceci imprime faux, si vous utilisiez le remplacement, il aurait été imprimé vrai.
(Code de base tiré de Joseph Daigle)
Donc, si vous faites un vrai polymorphisme vous DEVRAIENT TOUJOURS SUPPRIMER . Le seul endroit où vous devez utiliser "nouveau" est lorsque la méthode n'est en aucun cas liée à la version de la classe de base.
Je trouve toujours des choses comme celle-ci plus faciles à comprendre avec des images:
Encore une fois, en prenant le code de Joseph Daigle,
public class Foo
{
public /*virtual*/ bool DoSomething() { return false; }
}
public class Bar : Foo
{
public /*override or new*/ bool DoSomething() { return true; }
}
Si vous appelez ensuite le code comme ceci:
Foo a = new Bar();
a.DoSomething();
NOTE: L'important est que notre objet soit en fait une Bar
, mais nous sommes en le stockant dans une variable de type Foo
(cela revient à le lancer}
Le résultat sera alors le suivant, selon que vous ayez utilisé virtual
/override
ou new
lors de la déclaration de vos classes.
Voici un code pour comprendre la différence de comportement des méthodes virtuelles et non virtuelles:
class A
{
public void foo()
{
Console.WriteLine("A::foo()");
}
public virtual void bar()
{
Console.WriteLine("A::bar()");
}
}
class B : A
{
public new void foo()
{
Console.WriteLine("B::foo()");
}
public override void bar()
{
Console.WriteLine("B::bar()");
}
}
class Program
{
static int Main(string[] args)
{
B b = new B();
A a = b;
a.foo(); // Prints A::foo
b.foo(); // Prints B::foo
a.bar(); // Prints B::bar
b.bar(); // Prints B::bar
return 0;
}
}
Le mot clé new
crée en fait un membre entièrement nouveau qui n'existe que sur ce type spécifique.
Par exemple
public class Foo
{
public bool DoSomething() { return false; }
}
public class Bar : Foo
{
public new bool DoSomething() { return true; }
}
La méthode existe sur les deux types. Lorsque vous utilisez réflexion et récupérez les membres de type Bar
, vous trouverez 2 méthodes appelées DoSomething()
qui ont exactement la même apparence. En utilisant new
, vous masquez efficacement l'implémentation dans la classe de base. Ainsi, lorsque les classes dérivent de Bar
(dans mon exemple), l'appel de méthode à base.DoSomething()
passe à Bar
et non à Foo
.
virtual/override indique au compilateur que les deux méthodes sont liées et que, dans certaines circonstances, lorsque vous penseriez appeler la première méthode (virtuelle), il est en fait correct d'appeler la deuxième méthode (surchargée). C'est le fondement du polymorphisme.
(new SubClass() as BaseClass).VirtualFoo()
Appellera la méthode VirtualFoo () remplacée par la SubClass.
new indique au compilateur que vous ajoutez une méthode à une classe dérivée portant le même nom qu'une méthode de la classe de base, mais qu'ils n'ont pas de relation les uns avec les autres.
(new SubClass() as BaseClass).NewBar()
Appellera la méthode NewBar () de BaseClass, alors que:
(new SubClass()).NewBar()
Appellera la méthode NewBar () du SubClass.
Au-delà des détails techniques, je pense que l’utilisation de virtual/override communique beaucoup d’informations sémantiques sur la conception. Lorsque vous déclarez une méthode virtuelle, vous indiquez que vous vous attendez à ce que les classes d'implémentation veuillent fournir leurs propres implémentations, autres que celles par défaut. De même, omettre cela dans une classe de base déclare également que la méthode par défaut devrait suffire pour toutes les classes implémentées. De même, on peut utiliser des déclarations abstraites pour forcer les classes d'implémentation à fournir leur propre implémentation. Encore une fois, je pense que cela communique beaucoup sur la manière dont le programmeur s'attend à ce que le code soit utilisé. Si j'écrivais à la fois les classes de base et d'implémentation et que je me retrouvais avec de nouvelles méthodes, je repenserais sérieusement la décision de ne pas rendre la méthode virtuelle dans le parent et de déclarer mon intention de manière spécifique.
new
mot-clé est pour Cacher. - signifie que vous cachez votre méthode au moment de l'exécution. La sortie sera basée sur la méthode de la classe de base.override
pour passer outre. - signifie que vous appelez votre méthode de classe dérivée avec la référence de classe de base. La sortie sera basée sur la méthode de la classe dérivée.Ma version de l'explication provient de l'utilisation de properties pour aider à comprendre les différences.
override
est assez simple, non? Le type sous-jacent annule celui du parent.
new
est peut-être trompeur (pour moi c'était). Avec les propriétés, il est plus facile de comprendre:
public class Foo
{
public bool GetSomething => false;
}
public class Bar : Foo
{
public new bool GetSomething => true;
}
public static void Main(string[] args)
{
Foo foo = new Bar();
Console.WriteLine(foo.GetSomething);
Bar bar = new Bar();
Console.WriteLine(bar.GetSomething);
}
En utilisant un débogueur, vous remarquerez que Foo foo
a les propriétés 2 GetSomething
, car il possède deux versions de la propriété, Foo
's et Bar
' s, et savoir laquelle utiliser, c # "sélectionne" la propriété pour le type actuel.
Si vous vouliez utiliser la version de la barre, vous auriez utilisé le remplacement ou utiliser Foo foo
à la place.
Bar bar
a seulement 1 , car il veut complètement nouveau comportement pour GetSomething
.
Ne pas marquer une méthode avec quoi que ce soit signifie: Lier cette méthode en utilisant le type de compilation de l'objet, pas le type à l'exécution (liaison statique).
Marquer une méthode avec virtual
signifie: Lier cette méthode en utilisant le type d'exécution de l'objet, pas le type à la compilation (liaison dynamique).
Marquer une méthode de classe de base virtual
avec override
dans une classe dérivée signifie: Il s'agit de la méthode à lier à l'aide du type d'exécution de l'objet (liaison dynamique).
Marquer une méthode virtual
de classe de base avec new
dans une classe dérivée signifie: Il s’agit d’une nouvelle méthode qui n’a aucune relation avec celle qui porte le même nom dans la classe de base et doit être liée à l’aide du type de compilation de l’objet (liaison statique).
Ne pas marquer une méthode de classe de base virtual
dans la classe dérivée signifie: Cette méthode est marquée comme new
(liaison statique).
Marquer une méthode abstract
signifie: Cette méthode est virtuelle, mais je ne déclarerai pas de corps pour elle et sa classe est également abstraite (liaison dynamique).