我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。

为什么这是不该做的?


当前回答

在这个特定的例子中,C++和C#之间有区别。在C++中,对象未初始化,因此在构造函数中调用病毒函数是不安全的。在C#中,当创建类对象时,其所有成员都是零初始化的。可以在构造函数中调用虚拟函数,但如果您将访问仍然为零的成员。如果您不需要访问成员,那么在C#中调用虚拟函数是非常安全的。

其他回答

对于你为什么不想这样做,上面有很好的答案。这里有一个反例,也许你想这样做(由Sandi Metz翻译成C#,来自Ruby中的实用面向对象设计,第126页)。

注意,GetDependency()没有触及任何实例变量。如果静态方法可以是虚拟的,那么它将是静态的。

(公平地说,可能有更聪明的方法通过依赖注入容器或对象初始化器来实现这一点…)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

在C#中,基类构造函数在派生类构造函数之前运行,因此派生类可能在可能重写的虚拟成员中使用的任何实例字段都尚未初始化。

请注意,这只是一个警告,让你注意并确保一切正常。这个场景有实际的用例,您只需要记录虚拟成员的行为,它不能使用下面的派生类中声明的任何实例字段,构造函数调用它。

在这个特定的例子中,C++和C#之间有区别。在C++中,对象未初始化,因此在构造函数中调用病毒函数是不安全的。在C#中,当创建类对象时,其所有成员都是零初始化的。可以在构造函数中调用虚拟函数,但如果您将访问仍然为零的成员。如果您不需要访问成员,那么在C#中调用虚拟函数是非常安全的。

为了回答您的问题,请考虑以下问题:当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为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。

是的,在构造函数中调用虚拟方法通常是不好的。

此时,对象可能还没有完全构造,方法所期望的不变量可能还不成立。