Je reçois un avertissement de ReSharper sur un appel à un membre virtuel de mes objets constructeur. Pourquoi serait-ce une chose à ne pas faire?
Réponses
Trop de publicités?(En supposant que vous avez écrit en C# ici)
Lorsqu'un objet écrit en C# est construit, ce qui se passe est que les initialiseurs d'exécuter dans l'ordre de la plus classe dérivée de la classe de base, et puis les constructeurs exécuter dans l'ordre de la classe de base pour la plupart de la classe dérivée (voir Eric Lippert du blog pour plus de détails pourquoi c'est).
Aussi dans .NET les objets ne change pas de type comme ils sont construits, mais au départ comme la plupart des dérivés de type, avec la méthode de la table, pour la plupart, type dérivé. Cela signifie que la méthode virtuelle appelle toujours exécuté sur la plupart des dérivés de type.
Lorsque vous combinez ces deux éléments, vous êtes de gauche avec le problème que si vous avez une méthode virtuelle appel à un constructeur, et ce n'est pas la plus type dérivé dans sa hiérarchie d'héritage, qu'il sera appelé sur une classe dont le constructeur n'a pas été exécuté, et, par conséquent, peut ne pas être dans un état convenable d'avoir de la méthode appelée.
Ce problème est, bien sûr, atténués si vous marquez votre classe comme scellées pour s'assurer que c'est la plus type dérivé dans la hiérarchie d'héritage - dans ce cas, il est parfaitement possible d'appeler la méthode virtuelle.
Afin de répondre à votre question, pensez à cette question: quel sera le code ci-dessous imprimer lorsque l' Child
objet est instancié?
class Parent
{
public Parent()
{
DoSomething();
}
protected virtual void DoSomething() {};
}
class Child : Parent
{
private string foo;
public Child() { foo = "HELLO"; }
protected override void DoSomething()
{
Console.WriteLine(foo.ToLower());
}
}
La réponse est qu'en fait, un NullReferenceException
sera levée, car foo
a la valeur null. Un objet de la base de constructeur est appelé avant son propre constructeur. En ayant un virtual
appel à un constructeur de l'objet que vous êtes en introduisant la possibilité d'hériter objets d'exécuter du code avant d'avoir été entièrement initialisé.
Les règles de C# sont très différentes de celle de Java et de C++.
Lorsque vous êtes dans le constructeur pour un objet en C#, que l'objet existe dans un entièrement initialisé (tout simplement pas "construit"), comme son type dérivé.
namespace Demo
{
class A
{
public A()
{
System.Console.WriteLine("This is a {0},", this.GetType());
}
}
class B : A
{
}
// . . .
B b = new B(); // Output: "This is a Demo.B"
}
Cela signifie que si vous appelez une fonction virtuelle à partir du constructeur de A, il permettra de résoudre à tout remplacer dans B, si l'on est.
Même si vous avez intentionnellement mis en place A et B comme cela, la pleine compréhension du comportement du système, vous pourriez être dedans pour un choc plus tard. Dire que vous avez appelé des fonctions virtuelles dans le B du constructeur, "savoir" qu'ils seraient traités par B ou A selon le cas. Puis le temps passe, et quelqu'un d'autre décide qu'ils doivent définir C, et de remplacer certaines des fonctions virtuelles. Tout d'un coup B du constructeur finit par appeler le code en C, ce qui pourrait conduire à assez surprenant comportement.
C'est probablement une bonne idée pour éviter de fonctions virtuelles dans les constructeurs de toute façon, puisque les règles sont si différentes entre le C#, C++ et Java. Vos programmeurs ne peuvent pas savoir à quoi s'attendre!
Les raisons de l'avertissement sont déjà décrites, mais comment voulez-vous résoudre l'avertissement? Vous devez sceau de la classe ou de membre virtuel.
class B
{
protected virtual void Foo() { }
}
class A : B
{
public A()
{
Foo(); // warning here
}
}
Vous pouvez sceau de catégorie A:
sealed class A : B
{
public A()
{
Foo(); // no warning
}
}
Ou vous pouvez sceller la méthode Foo:
class A : B
{
public A()
{
Foo(); // no warning
}
protected sealed override void Foo()
{
base.Foo();
}
}
En C#, une classe de base' constructeur s'exécute avant la dérivée de la classe de constructeur, de sorte que toute instance de champs d'une classe dérivée peut utiliser dans le éventuellement substituée membre virtuel ne sont pas encore initialisé.
Notez que c'est juste un avertissement pour vous faire payer l'attention et assurez-vous qu'il est tout droit. Il existe effectivement des cas d'utilisation pour ce scénario, vous avez juste à documenter le comportement du membre virtuel qu'il ne peut pas utiliser n'importe champs d'instance a déclaré dans une classe dérivée ci-dessous où le constructeur de l'appeler.