Virtual member call in a constructor

后端 未结 18 1907
[愿得一人]
[愿得一人] 2020-11-22 01:27

I\'m getting a warning from ReSharper about a call to a virtual member from my objects constructor.

Why would this be something not to do?

相关标签:
18条回答
  • 2020-11-22 01:56

    In order to answer your question, consider this question: what will the below code print out when the Child object is instantiated?

    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!?!
        }
    }
    

    The answer is that in fact a NullReferenceException will be thrown, because foo is null. An object's base constructor is called before its own constructor. By having a virtual call in an object's constructor you are introducing the possibility that inheriting objects will execute code before they have been fully initialized.

    0 讨论(0)
  • 2020-11-22 01:56

    In C#, a base class' constructor runs before the derived class' constructor, so any instance fields that a derived class might use in the possibly-overridden virtual member are not initialized yet.

    Do note that this is just a warning to make you pay attention and make sure it's all-right. There are actual use-cases for this scenario, you just have to document the behavior of the virtual member that it can not use any instance fields declared in a derived class below where the constructor calling it is.

    0 讨论(0)
  • 2020-11-22 01:57

    Beware of blindly following Resharper's advice and making the class sealed! If it's a model in EF Code First it will remove the virtual keyword and that would disable lazy loading of it's relationships.

        public **virtual** User User{ get; set; }
    
    0 讨论(0)
  • 2020-11-22 02:01

    When an object written in C# is constructed, what happens is that the initializers run in order from the most derived class to the base class, and then constructors run in order from the base class to the most derived class (see Eric Lippert's blog for details as to why this is).

    Also in .NET objects do not change type as they are constructed, but start out as the most derived type, with the method table being for the most derived type. This means that virtual method calls always run on the most derived type.

    When you combine these two facts you are left with the problem that if you make a virtual method call in a constructor, and it is not the most derived type in its inheritance hierarchy, that it will be called on a class whose constructor has not been run, and therefore may not be in a suitable state to have that method called.

    This problem is, of course, mitigated if you mark your class as sealed to ensure that it is the most derived type in the inheritance hierarchy - in which case it is perfectly safe to call the virtual method.

    0 讨论(0)
  • 2020-11-22 02:02

    One important missing bit is, what is the correct way to resolve this issue?

    As Greg explained, the root problem here is that a base class constructor would invoke the virtual member before the derived class has been constructed.

    The following code, taken from MSDN's constructor design guidelines, demonstrates this issue.

    public class BadBaseClass
    {
        protected string state;
    
        public BadBaseClass()
        {
            this.state = "BadBaseClass";
            this.DisplayState();
        }
    
        public virtual void DisplayState()
        {
        }
    }
    
    public class DerivedFromBad : BadBaseClass
    {
        public DerivedFromBad()
        {
            this.state = "DerivedFromBad";
        }
    
        public override void DisplayState()
        {   
            Console.WriteLine(this.state);
        }
    }
    

    When a new instance of DerivedFromBad is created, the base class constructor calls to DisplayState and shows BadBaseClass because the field has not yet been update by the derived constructor.

    public class Tester
    {
        public static void Main()
        {
            var bad = new DerivedFromBad();
        }
    }
    

    An improved implementation removes the virtual method from the base class constructor, and uses an Initialize method. Creating a new instance of DerivedFromBetter displays the expected "DerivedFromBetter"

    public class BetterBaseClass
    {
        protected string state;
    
        public BetterBaseClass()
        {
            this.state = "BetterBaseClass";
            this.Initialize();
        }
    
        public void Initialize()
        {
            this.DisplayState();
        }
    
        public virtual void DisplayState()
        {
        }
    }
    
    public class DerivedFromBetter : BetterBaseClass
    {
        public DerivedFromBetter()
        {
            this.state = "DerivedFromBetter";
        }
    
        public override void DisplayState()
        {
            Console.WriteLine(this.state);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:03

    Reasons of the warning are already described, but how would you fix the warning? You have to seal either class or virtual member.

      class B
      {
        protected virtual void Foo() { }
      }
    
      class A : B
      {
        public A()
        {
          Foo(); // warning here
        }
      }
    

    You can seal class A:

      sealed class A : B
      {
        public A()
        {
          Foo(); // no warning
        }
      }
    

    Or you can seal method Foo:

      class A : B
      {
        public A()
        {
          Foo(); // no warning
        }
    
        protected sealed override void Foo()
        {
          base.Foo();
        }
      }
    
    0 讨论(0)
提交回复
热议问题