我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
当前回答
警告的原因已经描述过了,但您将如何修复警告?您必须密封类或虚拟成员。
class B
{
protected virtual void Foo() { }
}
class A : B
{
public A()
{
Foo(); // warning here
}
}
您可以密封A类:
sealed class A : B
{
public A()
{
Foo(); // no warning
}
}
或者您可以密封方法Foo:
class A : B
{
public A()
{
Foo(); // no warning
}
protected sealed override void Foo()
{
base.Foo();
}
}
其他回答
我只需要向基类添加一个Initialize()方法,然后从派生构造函数调用它。在执行所有构造函数之后,该方法将调用任何虚拟/抽象方法/财产:)
因为在构造函数完成执行之前,对象不会完全实例化。虚拟函数引用的任何成员都不能初始化。在C++中,当您处于构造函数中时,这仅指所处构造函数的静态类型,而不是所创建对象的实际动态类型。这意味着虚拟函数调用甚至可能不会到达您期望的位置。
重要的一点是,解决这个问题的正确方法是什么?
正如Greg所解释的,这里的根本问题是基类构造函数会在构造派生类之前调用虚拟成员。
以下代码摘自MSDN的构造函数设计指南,演示了这个问题。
public class BadBaseClass
{
protected string state;
public BadBaseClass()
{
this.state = "BadBaseClass";
this.DisplayState();
}
public virtual void DisplayState()
{
}
}
public class DerivedFromBad : BadBaseClass
{
public DerivedFromBad()
{
this.state = "DerivedFromBad";
}
public override void DisplayState()
{
Console.WriteLine(this.state);
}
}
创建DerivedFromBad的新实例时,基类构造函数调用DisplayState并显示BadBaseClass,因为派生构造函数尚未更新该字段。
public class Tester
{
public static void Main()
{
var bad = new DerivedFromBad();
}
}
一个改进的实现从基类构造函数中删除了虚拟方法,并使用了Initialize方法。创建DerivedFromBetter的新实例将显示预期的“DerivedFromBetter”
public class BetterBaseClass
{
protected string state;
public BetterBaseClass()
{
this.state = "BetterBaseClass";
this.Initialize();
}
public void Initialize()
{
this.DisplayState();
}
public virtual void DisplayState()
{
}
}
public class DerivedFromBetter : BetterBaseClass
{
public DerivedFromBetter()
{
this.state = "DerivedFromBetter";
}
public override void DisplayState()
{
Console.WriteLine(this.state);
}
}
是的,在构造函数中调用虚拟方法通常是不好的。
此时,对象可能还没有完全构造,方法所期望的不变量可能还不成立。
我认为,如果您想让子类能够设置或重写父构造函数将立即使用的属性,则忽略警告可能是合法的:
internal class Parent
{
public Parent()
{
Console.WriteLine("Parent ctor");
Console.WriteLine(Something);
}
protected virtual string Something { get; } = "Parent";
}
internal class Child : Parent
{
public Child()
{
Console.WriteLine("Child ctor");
Console.WriteLine(Something);
}
protected override string Something { get; } = "Child";
}
这里的风险是子类从其构造函数设置属性,在这种情况下,值的更改将在调用基类构造函数之后发生。
我的用例是,我希望子类提供一个特定的值或一个实用程序类(如转换器),而不需要在基础上调用初始化方法。
在实例化子类时,上面的输出是:
Parent ctor
Child
Child ctor
Child