关于c#:在为属性赋值时,在构造函数中调用虚拟成员

Virtual member call in a constructor when assigning value to property

我有一个抽象类和一个派生类。抽象类定义名为message的抽象属性。在派生类中,通过重写抽象属性来实现属性。派生类的构造函数接受一个字符串参数并将其赋给其消息属性。在Resharper中,此分配导致警告"虚成员调用构造函数"。

AbstractClass具有以下定义:

1
2
3
4
5
6
7
public abstract class AbstractClass {
    public abstract string Message { get; set; }

    protected AbstractClass() {}

    public abstract void PrintMessage();
}

派生类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

我确实找到了一些关于这个警告的其他问题,但是在那些情况下,有一个方法的实际调用。例如,在这个问题中,MattHowels的答案包含一些示例代码。我在这里重复一遍,以便参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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没有描述警告会出现什么错误,但我假设它会在父构造函数中调用dosomething。在这个例子中,我理解被调用的虚拟成员的含义。成员调用发生在基类中,其中只存在虚拟方法。

然而,在我的情况下,我不明白为什么给Message赋值会调用虚拟成员。对消息属性的调用和实现都在派生类中定义。

虽然我可以通过生成派生类sealed来消除错误,但我想了解这种情况导致警告的原因。

更新基于Brett的回答,我尽力创建了一个派生类派生的子类,它最终会导致异常。这就是我想到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
        }
    }
}

当然,在消息设置器中使用_foo有点傻,但关键是resharper没有发现这个类有任何问题。

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

1
2
3
4
5
6
internal class Program {
    private static void Main() {
        var childClass = new ChildClass();
        childClass.PrintMessage();
    }
}

在创建ChildClass对象时,将得到一个NullReferenceException。由于_foo尚未初始化,子类使用_foo.ToUpper()的尝试将引发异常。


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

这就是为什么让你的DerivedClass密封解决了这个问题——它不能被继承。