我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
当前回答
为了回答您的问题,请考虑以下问题:当Child对象被实例化时,下面的代码将打印出什么?
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()); //NullReferenceException!?!
}
}
答案是,实际上会引发NullReferenceException,因为foo为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。
其他回答
为了回答您的问题,请考虑以下问题:当Child对象被实例化时,下面的代码将打印出什么?
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()); //NullReferenceException!?!
}
}
答案是,实际上会引发NullReferenceException,因为foo为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。
警告的原因已经描述过了,但您将如何修复警告?您必须密封类或虚拟成员。
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()方法,然后从派生构造函数调用它。在执行所有构造函数之后,该方法将调用任何虚拟/抽象方法/财产:)
该警告提醒您虚拟成员很可能在派生类上被重写。在这种情况下,父类对虚拟成员所做的任何操作都将通过重写子类来撤消或更改。看一下这个小例子
下面的父类试图将值设置为其构造函数上的虚拟成员。这将触发Re sharper警告,请参见代码:
public class Parent
{
public virtual object Obj{get;set;}
public Parent()
{
// Re-sharper warning: this is open to change from
// inheriting class overriding virtual member
this.Obj = new Object();
}
}
此处的子类重写父属性。如果此属性未标记为virtual,编译器将警告该属性隐藏父类上的属性,如果有意,建议您添加“new”关键字。
public class Child: Parent
{
public Child():base()
{
this.Obj = "Something";
}
public override object Obj{get;set;}
}
最后是对使用的影响,下面示例的输出放弃了父类构造函数设置的初始值。这就是Re sharper试图警告您的,在父类构造函数上设置的值将被子类构造函数覆盖,子类构造函数在父类构造器之后立即调用。
public class Program
{
public static void Main()
{
var child = new Child();
// anything that is done on parent virtual member is destroyed
Console.WriteLine(child.Obj);
// Output: "Something"
}
}