Je suis un peu confus au sujet de la substitution par rapport à la dissimulation d'une méthode en C #. Des utilisations pratiques de chacun seraient également appréciées, ainsi qu'une explication de quand on utiliserait chacun.
Je suis confus au sujet de la dérogation - pourquoi passons-nous outre? Ce que j'ai appris jusqu'à présent, c'est qu'en évitant, nous pouvons fournir l'implémentation souhaitée à une méthode d'une classe dérivée, sans changer la signature.
Si je ne remplace pas la méthode de la superclasse et que j'apporte des modifications à la méthode dans la sous-classe, cela apportera-t-il des modifications à la méthode de la super classe?
Je suis également confus sur les points suivants - qu'est-ce que cela démontre?
class A
{
virtual m1()
{
console.writeline("Bye to all");
}
}
class B : A
{
override m1()
{
console.writeLine("Hi to all");
}
}
class C
{
A a = new A();
B b = new B();
a = b; (what is this)
a.m1(); // what this will print and why?
b = a; // what happens here?
}
Considérer:
public class BaseClass
{
public void WriteNum()
{
Console.WriteLine(12);
}
public virtual void WriteStr()
{
Console.WriteLine("abc");
}
}
public class DerivedClass : BaseClass
{
public new void WriteNum()
{
Console.WriteLine(42);
}
public override void WriteStr()
{
Console.WriteLine("xyz");
}
}
/* ... */
BaseClass isReallyBase = new BaseClass();
BaseClass isReallyDerived = new DerivedClass();
DerivedClass isClearlyDerived = new DerivedClass();
isReallyBase.WriteNum(); // writes 12
isReallyBase.WriteStr(); // writes abc
isReallyDerived.WriteNum(); // writes 12
isReallyDerived.WriteStr(); // writes xyz
isClearlyDerived.WriteNum(); // writes 42
isClearlyDerived.writeStr(); // writes xyz
La substitution est la manière classique OO dans laquelle une classe dérivée peut avoir un comportement plus spécifique qu'une classe de base (dans certains langages, vous n'avez pas d'autre choix que de le faire). Lorsqu'une méthode virtuelle est appelée sur un objet, la version la plus dérivée de la méthode est appelée. Par conséquent, même si nous avons affaire à isReallyDerived
en tant que BaseClass
, la fonctionnalité définie dans DerivedClass
est utilisée.
Cacher signifie que nous avons une méthode complètement différente. Lorsque nous appelons WriteNum()
sur isReallyDerived
, il n'y a aucun moyen de savoir qu'il existe une WriteNum()
sur DerivedClass
différente, donc elle n'est pas appelée. Il ne peut être appelé que lorsque nous avons affaire à l'objet as a DerivedClass
.
La plupart du temps, se cacher est mauvais. Généralement, vous devez avoir une méthode aussi virtuelle si elle est susceptible d'être modifiée dans une classe dérivée et la remplacer dans la classe dérivée. Il y a cependant deux choses pour lesquelles il est utile:
Compatibilité ascendante. Si DerivedClass
avait une méthode DoStuff()
, puis plus tard BaseClass
a été modifié pour ajouter une méthode DoStuff()
, (rappelez-vous qu'ils peuvent être écrits par différentes personnes et existent dans différentes assemblées), alors une interdiction de masquer les membres aurait soudainement rendu DerivedClass
bogué sans que cela change. De plus, si la nouvelle DoStuff()
sur BaseClass
était virtuelle, le faire automatiquement sur DerivedClass
une dérogation pourrait conduire à l'appel de la méthode préexistante alors qu'elle ne devrait pas 't. Par conséquent, il est bon que le masquage soit la valeur par défaut (nous utilisons new
pour indiquer clairement que nous voulons vraiment nous cacher, mais le laisser masqué et émet un avertissement lors de la compilation).
Covariance du pauvre. Considérez une méthode Clone()
sur BaseClass
qui renvoie une nouvelle BaseClass
qui est une copie de celle créée. Dans le remplacement de DerivedClass
, cela créera un DerivedClass
mais le renverra comme un BaseClass
, ce qui n'est pas aussi utile. Ce que nous pourrions faire, c'est d'avoir une CreateClone()
virtuelle protégée qui est surchargée. Dans BaseClass
nous avons une Clone()
qui retourne le résultat de ceci - et tout va bien - dans DerivedClass
nous cachons ceci avec une nouvelle Clone()
qui renvoie un DerivedClass
. L'appel de Clone()
sur BaseClass
renverra toujours une référence BaseClass
, qui sera une valeur BaseClass
ou une valeur DerivedClass
selon le cas. L'appel de Clone()
sur DerivedClass
renverra une valeur DerivedClass
, ce que nous souhaiterions dans ce contexte. Il existe d'autres variantes de ce principe, mais il convient de noter qu'elles sont toutes assez rares.
Une chose importante à noter avec le deuxième cas, c'est que nous avons utilisé le masquage précisément pour supprimer surprendre le code appelant, car la personne utilisant DerivedClass
pourrait raisonnablement s'attendre à sa fonction Clone()
pour renvoyer un DerivedClass
. Les résultats de l'une des façons dont il pourrait être appelé sont maintenus cohérents les uns avec les autres. La plupart des cas de dissimulation risquent d'introduire des surprises, c'est pourquoi ils sont généralement mal vus. Celui-ci est justifié précisément parce qu'il résout le problème même que la dissimulation introduit souvent.
En tout, se cacher est parfois nécessaire, rarement utile, mais généralement mauvais, alors soyez très prudent.
La substitution est lorsque vous fournissez une nouvelle implémentation override
d'une méthode dans une classe descendante lorsque cette méthode est définie dans la classe de base comme virtual
.
Le masquage est lorsque vous fournissez une nouvelle implémentation d'une méthode dans une classe descendante lorsque cette méthode est non définie dans la classe de base comme virtual
, ou lorsque votre nouvelle implémentation ne spécifie pas override
.
Se cacher est très souvent mauvais; vous devriez généralement essayer de ne pas le faire si vous pouvez l'éviter du tout. Le masquage peut provoquer des événements inattendus, car les méthodes cachées ne sont utilisées que lorsqu'elles sont appelées sur une variable du type réel que vous avez défini, pas si vous utilisez une référence de classe de base ... d'autre part, les méthodes virtuelles qui sont remplacées se retrouveront avec la version de méthode appropriée étant appelée, même lorsqu'elle est appelée à l'aide de la référence de classe de base sur une classe enfant.
Par exemple, considérez ces classes:
public class BaseClass
{
public virtual void Method1() //Virtual method
{
Console.WriteLine("Running BaseClass Method1");
}
public void Method2() //Not a virtual method
{
Console.WriteLine("Running BaseClass Method2");
}
}
public class InheritedClass : BaseClass
{
public override void Method1() //Overriding the base virtual method.
{
Console.WriteLine("Running InheritedClass Method1");
}
public new void Method2() //Can't override the base method; must 'new' it.
{
Console.WriteLine("Running InheritedClass Method2");
}
}
Appelons-le comme ceci, avec une instance de InheritedClass, dans une référence correspondante:
InheritedClass inherited = new InheritedClass();
inherited.Method1();
inherited.Method2();
Cela renvoie ce à quoi vous devez vous attendre; les deux méthodes indiquent qu'elles exécutent les versions InheritedClass.
Exécution de la méthode InheritedClass1
Exécution de la méthode InheritedClass2
Ce code crée une instance de la même, InheritedClass, mais la stocke dans une référence BaseClass:
BaseClass baseRef = new InheritedClass();
baseRef.Method1();
baseRef.Method2();
Normalement, sous les principes OOP, vous devriez vous attendre à la même sortie que l'exemple ci-dessus. Mais vous n'obtenez pas la même sortie:
Exécution de la méthode InheritedClass1
Exécution de la méthode BaseClass2
Lorsque vous avez écrit le code InheritedClass, vous souhaitiez peut-être que tous les appels à Method2()
exécutent le code que vous y avez écrit. Normalement, c'est ainsi que cela fonctionne - en supposant que vous travaillez avec une méthode virtual
que vous avez remplacée. Mais comme vous utilisez une méthode new
/hidden, elle appelle à la place la version sur la référence que vous utilisez.
Si c'est le comportement que vous voulez vraiment, alors; Voilà. Mais je suggère fortement que si c'est ce que vous voulez, il peut y avoir un problème architectural plus important avec le code.
La substitution de méthode consiste simplement à remplacer une implémentation par défaut d'une méthode de classe de base dans la classe dérivée.
Masquage de méthode: vous pouvez utiliser le mot-clé "nouveau" avant une méthode virtuelle dans une classe dérivée
comme
class Foo
{
public virtual void foo1()
{
}
}
class Bar:Foo
{
public new virtual void foo1()
{
}
}
maintenant, si vous créez une autre classe Bar1 dérivée de Bar, vous pouvez remplacer foo1 qui est défini dans Bar.