关于c#:构造函数中的虚拟成员调用 – 为什么一个好,另一个不行?


Virtual member call in constructor - why is one okay and the other not?

使用下面的代码,Resharper引发"虚成员调用构造函数"警告:

1
2
3
4
5
6
7
8
public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        // DisplayName is a virtual member of Screen
        DisplayName ="User Details";
    }
}

但是,如果我更改了代码,这样的话,警告就会消失:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        SetName();
    }

    private void SetName()
    {
        DisplayName ="User Details";
    }
}

为什么一个提出警告而另一个没有?第二种方法是正确的,还是仅仅超出了Resharper检测到的潜在危险的极限?


这仅仅是一个重新竖琴的限制。它应用了一系列的启发式方法来查找可以发出警告的代码,但并不能找到所有的代码。这样做需要解决暂停问题。这是不可行的。

这里的教训很简单:没有警告并不意味着没有什么可警告的。


在caliburn.micro中,通常通过重写oninitialize()方法来设置displayname,例如

1
2
3
4
5
    protected override void OnInitialize()
    {
        DisplayName ="User Details";
        base.OnInitialize();
    }

OnInitialize()只被调用一次,并且不再在Resharper中引发警告。这里也可以看到。


由于以下情况可能出现的问题而发出警告

  • 一个派生类型为UserDetailViewModel的对象,表示正在构造"concreteModel"

  • ConcreteModel覆盖Screen.DisplayName属性。在属性的set方法中,它依赖于已完成构造的ConcreteModel,比如它访问在构造函数中初始化的另一个成员。

在这种情况下,上述代码将引发异常。

解决这个问题的正确方法是在UserDetailViewModel中声明DisplayNamesealed。现在您可以确信忽略警告是可以的。

下面的示例演示了它。取消对Der中的行的注释会导致编译错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Base
{
    public virtual string DisplayName { get; set; }
}

class Der : Base
{
    public Der()
    {
        //  ok to ignore virtual member access here
        DisplayName ="Der";
    }
    public override sealed string DisplayName { get; set; }
}

class Leaf : Der
{
    private string _displayName;
    public Leaf()
    {
        _displayName ="default";
    }
    //override public string DisplayName
    //{
    //    get { return _displayName; }
    //    set { if (!_displayName.Equals(value)) _displayName = value; }
    //}
}