我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
当前回答
C#的规则与Java和C++的规则非常不同。
当您在C#中的某个对象的构造函数中时,该对象以完全初始化(只是不是“构造”)的形式存在,作为其完全派生类型。
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"
}
这意味着,如果从a的构造函数调用虚拟函数,它将解析为B中的任何重写(如果提供了重写)。
即使你有意这样设置A和B,充分理解系统的行为,你也可能会在以后受到冲击。假设您在B的构造函数中调用了虚拟函数,“知道”它们将由B或A酌情处理。然后时间过去了,其他人决定他们需要定义C,并重写其中的一些虚拟函数。突然间,B的构造函数最终调用了C中的代码,这可能会导致非常令人惊讶的行为。
无论如何,避免构造函数中的虚函数可能是一个好主意,因为C#、C++和Java之间的规则是如此不同。你的程序员可能不知道该怎么做!
其他回答
小心盲目地听从Resharper的建议,让课堂变得封闭!如果它是EF Code First中的模型,它将删除虚拟关键字,这将禁用其关系的延迟加载。
public **virtual** User User{ get; set; }
为了回答您的问题,请考虑以下问题:当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为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。
我认为,如果您想让子类能够设置或重写父构造函数将立即使用的属性,则忽略警告可能是合法的:
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
是的,在构造函数中调用虚拟方法通常是不好的。
此时,对象可能还没有完全构造,方法所期望的不变量可能还不成立。
该警告提醒您虚拟成员很可能在派生类上被重写。在这种情况下,父类对虚拟成员所做的任何操作都将通过重写子类来撤消或更改。看一下这个小例子
下面的父类试图将值设置为其构造函数上的虚拟成员。这将触发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"
}
}