Virtual member call in a constructor

后端 未结 18 1929
[愿得一人]
[愿得一人] 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 02:03

    One important aspect of this question which other answers have not yet addressed is that it is safe for a base-class to call virtual members from within its constructor if that is what the derived classes are expecting it to do. In such cases, the designer of the derived class is responsible for ensuring that any methods which are run before construction is complete will behave as sensibly as they can under the circumstances. For example, in C++/CLI, constructors are wrapped in code which will call Dispose on the partially-constructed object if construction fails. Calling Dispose in such cases is often necessary to prevent resource leaks, but Dispose methods must be prepared for the possibility that the object upon which they are run may not have been fully constructed.

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

    Another interesting thing I found is that the ReSharper error can be 'satisfied' by doing something like below which is dumb to me. However, as mentioned by many earlier, it still is not a good idea to call virtual properties/methods in constructor.

    public class ConfigManager
    {
       public virtual int MyPropOne { get; private set; }
       public virtual string MyPropTwo { get; private set; }
    
       public ConfigManager()
       {
        Setup();
       }
    
       private void Setup()
       {
        MyPropOne = 1;
        MyPropTwo = "test";
       }
    }
    
    0 讨论(0)
  • 2020-11-22 02:05

    Because until the constructor has completed executing, the object is not fully instantiated. Any members referenced by the virtual function may not be initialised. In C++, when you are in a constructor, this only refers to the static type of the constructor you are in, and not the actual dynamic type of the object that is being created. This means that the virtual function call might not even go where you expect it to.

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

    I think that ignoring the warning might be legitimate if you want to give the child class the ability to set or override a property that the parent constructor will use right away:

    internal class Parent
    {
        public Parent()
        {
            Console.WriteLine("Parent ctor");
            Console.WriteLine(Something);
        }
    
        protected virtual string Something { get; } = "Parent";
    }
    
    internal class Child : Parent
    {
        public Child()
        {
            Console.WriteLine("Child ctor");
            Console.WriteLine(Something);
        }
    
        protected override string Something { get; } = "Child";
    }
    

    The risk here would be for the child class to set the property from its constructor in which case the change in the value would occur after the base class constructor has been called.

    My use case is that I want the child class to provide a specific value or a utility class such as a converter and I don't want to have to call an initialization method on the base.

    The output of the above when instantiating the child class is:

    Parent ctor
    Child
    Child ctor
    Child
    
    0 讨论(0)
  • 2020-11-22 02:07

    There are well-written answers above for why you wouldn't want to do that. Here's a counter-example where perhaps you would want to do that (translated into C# from Practical Object-Oriented Design in Ruby by Sandi Metz, p. 126).

    Note that GetDependency() isn't touching any instance variables. It would be static if static methods could be virtual.

    (To be fair, there are probably smarter ways of doing this via dependency injection containers or object initializers...)

    public class MyClass
    {
        private IDependency _myDependency;
    
        public MyClass(IDependency someValue = null)
        {
            _myDependency = someValue ?? GetDependency();
        }
    
        // If this were static, it could not be overridden
        // as static methods cannot be virtual in C#.
        protected virtual IDependency GetDependency() 
        {
            return new SomeDependency();
        }
    }
    
    public class MySubClass : MyClass
    {
        protected override IDependency GetDependency()
        {
            return new SomeOtherDependency();
        }
    }
    
    public interface IDependency  { }
    public class SomeDependency : IDependency { }
    public class SomeOtherDependency : IDependency { }
    
    0 讨论(0)
  • 2020-11-22 02:08

    The rules of C# are very different from that of Java and C++.

    When you are in the constructor for some object in C#, that object exists in a fully initialized (just not "constructed") form, as its fully derived type.

    namespace Demo
    {
        class A 
        {
          public A()
          {
            System.Console.WriteLine("This is a {0},", this.GetType());
          }
        }
    
        class B : A
        {      
        }
    
        // . . .
    
        B b = new B(); // Output: "This is a Demo.B"
    }
    

    This means that if you call a virtual function from the constructor of A, it will resolve to any override in B, if one is provided.

    Even if you intentionally set up A and B like this, fully understanding the behavior of the system, you could be in for a shock later. Say you called virtual functions in B's constructor, "knowing" they would be handled by B or A as appropriate. Then time passes, and someone else decides they need to define C, and override some of the virtual functions there. All of a sudden B's constructor ends up calling code in C, which could lead to quite surprising behavior.

    It is probably a good idea to avoid virtual functions in constructors anyway, since the rules are so different between C#, C++, and Java. Your programmers may not know what to expect!

    0 讨论(0)
提交回复
热议问题