【问题标题】:Virtual member call in constructor - why is one okay and the other not?构造函数中的虚拟成员调用 - 为什么一个可以而另一个不行?
【发布时间】:2011-10-07 11:16:50
【问题描述】:

使用下面的代码,Resharper 会引发“构造函数中的虚拟成员调用”警告:

public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        // DisplayName is a virtual member of Screen
        DisplayName = "User Details"; 
    }
}

而如果我将代码更改为这样,警告就会消失:

public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        SetName();
    }

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

为什么一个发出警告而另一个不发出警告?第二种方法在某种程度上是正确的,还是只是超出了 ReSharper 可以检测为潜在危险的限制?

【问题讨论】:

    标签: c#


    【解决方案1】:

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

    • 派生类型为UserDetailViewModel 的对象,例如“ConcreteModel` 正在构建中

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

    在这种情况下,上面的代码会抛出异常。

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

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

    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; }
        //}
    }
    

    【讨论】:

      【解决方案2】:

      这只是 ReSharper 的一个限制。它应用一堆启发式方法来查找可以发出警告的代码,但它不会找到所有内容。这样做需要解决停机问题。这是不可行的。

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

      【讨论】:

      • 谢谢 jalf。为避免此问题,是否可以将SetName() 更改为公开并在实例化后调用它,例如:userDetailViewModel.SetName();?还是有更好的解决方案? (这应该是一个新问题,我知道)!
      • 有可能发现构造函数可以潜在地调用一个虚拟成员而不做暂停分析。
      • @Town new UserDetailViewModel().SetName() 不是一个好的选择,因为它使调用者有责任调用SetName()。我会选择忽略警告。
      • @Hemal Pandya:我也觉得不对劲,尽管忽略这样的警告似乎不对!
      • 为什么DisplayName 必须是虚拟的?这对我来说似乎很奇怪。如果它是对象初始化的一部分,那么它几乎没有理由是虚拟的。
      【解决方案3】:

      在 Caliburn.Micro 中,通常通过覆盖 OnInitialize() 方法来设置 DisplayName,例如

          protected override void OnInitialize()
          {
              DisplayName = "User Details";
              base.OnInitialize();
          }
      

      OnInitialize() 仅被调用一次,并且您不再在 Resharper 中发出警告。另见here

      【讨论】:

        猜你喜欢
        • 2010-09-12
        • 1970-01-01
        • 1970-01-01
        • 2019-05-06
        • 2010-10-02
        • 2015-06-27
        • 2011-04-08
        • 2012-08-15
        相关资源
        最近更新 更多