【问题标题】:Virtual member call in a constructor when assigning value to property为属性赋值时在构造函数中调用虚拟成员
【发布时间】:2011-01-07 12:50:04
【问题描述】:

我有一个抽象类和一个派生类。抽象类定义了一个名为 Message 的抽象属性。在派生类中,属性是通过覆盖抽象属性来实现的。派生类的构造函数接受一个字符串参数并将其分配给它的 Message 属性。在 Resharper 中,此分配会导致警告“构造函数中的虚拟成员调用”。

AbstractClass 有这样的定义:

public abstract class AbstractClass {
    public abstract string Message { get; set; }

    protected AbstractClass() {}

    public abstract void PrintMessage();
}

而DerivedClass如下:

using System;

public class DerivedClass : AbstractClass {
    private string _message;

    public override string Message {
        get { return _message; }
        set { _message = value; }
    }

    public DerivedClass(string message) {
        Message = message; // Warning: Virtual member call in a constructor
    }

    public DerivedClass() : this("Default DerivedClass message") {}

    public override void PrintMessage() {
        Console.WriteLine("DerivedClass PrintMessage(): " + Message);
    }
}

我确实发现了有关此警告的其他一些问题,但在这些情况下,实际调用了一个方法。例如,在this question 中,Matt Howels 的回答包含一些示例代码。为了方便参考,我在这里重复一遍。

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());
    }
}

Matt 没有描述警告会出现什么错误,但我假设它将在 Parent 构造函数中调用 DoSomething。在此示例中,我了解调用虚拟成员的含义。成员调用发生在基类中,其中只存在一个虚方法。

但是,在我的情况下,我不明白为什么将值分配给 Message 会调用虚拟成员。 Message 属性的调用和实现都在派生类中定义。

虽然我可以通过创建派生类sealed 来消除错误,但我想了解为什么这种情况会导致警告。

更新 根据 Brett 的回答,我尽我所能创建了一个派生自 DerivedClass 的 ChildClass,它最终会导致异常。这是我想出的:

using System;

public class ChildClass : DerivedClass {
    private readonly string _foo;

    public ChildClass() : base("Default ChildClass Message") {
        _foo = "ChildClass foo";
    }

    public override string Message {
        get { return base.Message; }
        set {
            base.Message = value;
            Console.WriteLine(_foo.ToUpper() + " received " + value);
        }
    }
}

当然,在 Message setter 中使用 _foo 有点傻,但关键是 ReSharper 没有发现这个类有什么问题。

但是,如果您尝试在这样的程序中使用 ChildClass:

internal class Program {
    private static void Main() {
        var childClass = new ChildClass();
        childClass.PrintMessage();
    }
}

在创建 ChildClass 对象时,您将收到 NullReferenceException。 ChildClass 尝试使用_foo.ToUpper() 将引发异常,因为_foo 尚未初始化。

【问题讨论】:

    标签: c# properties resharper virtual abstract-class


    【解决方案1】:

    这是因为您的 Message 属性可以被 class ChildClass : DerivedClass 覆盖 - 此时可以从 DerivedClass 中的 ctor 调用 ChildClass 上的 Message 中的代码,并且您的 ChildClass 实例可能未完全初始化。

    这就是为什么让你的DerivedClass 密封可以解决问题 - 它不能被继承。

    【讨论】:

    • 这很奇怪。为什么 ChildClass 在 DerivedClass 中不是虚拟的时可以覆盖我的 Message 属性?覆盖是隐式虚拟的吗?
    • 你覆盖了实现,你并没有改变它是一个虚拟成员的事实。所以,是的。
    • 您也可以在覆盖时将方法标记为sealed,这意味着没有人可以在后代类中覆盖它——因此它实际上不再是虚拟的。这也应该消除错误。
    • @Joe:好点子。我想知道是否有办法让那个方法不再是虚拟的。
    • 今天我注意到如果我创建一个表单并在构造函数中设置其Text 属性,我什至会收到警告。这不是很正常的事情吗?如果表单应该有标题,我会在构造函数(或从构造函数调用的方法)中设置 Text 属性。
    猜你喜欢
    • 2019-05-06
    • 2014-09-13
    • 2010-09-12
    • 1970-01-01
    • 2010-10-02
    • 2015-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多