views:

162

answers:

7

I was surprised to find out that the parameter-less constructor of my base class is called any time I call any constructor in a derived class. I thought that is what : base() was for, to explicitly call the base constructor if and when I want to.

How can I prevent the base constructor from being called when I instantiate a derived class?

using System;

namespace TestConstru22323
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer("Jim", "Smith");
            Customer c = new Customer();
            Console.WriteLine(customer.Display());

            Console.ReadLine();
        }
    }

    public class Person
    {
        public Person()
        {
            Console.WriteLine("I don't always want this constructor to be called. I want to call it explicitly.");
        }
    }

    public class Customer : Person
    {
        private string _firstName;
        private string _lastName;

        //public Customer(string firstName, string lastName): base()  //this explicitly calls base empty constructor
        public Customer(string firstName, string lastName) //but this calls it anyway, why?
        {
            _firstName = firstName;
            _lastName = lastName;
        }

        public string Display()
        {
            return String.Format("{0}, {1}", _lastName, _firstName);
        }
    }
}
A: 

One way would be to make your base default constructor private, but that only works if you make a helper constructor in the base class that calls the private one when you need it explicitly.

class Base
{
  private Base() { ... special default base logic ... }
  protected Base(... params ...) : this() { ... exposed base that calls private cons ... }
  protected Base(... other params ...) /* no default call */ { ... exposed cons that doesnt call default ...}
}

class DerivedNoCall
{
  public DerivedNoCall() : base(... other params ...) {} //<-- will NOT call default base
}

class DerivedDoCall
{
  public DerivedDoCall() : base(... params ...) {} //<-- WILL call default base cons indirectly
}

This is really contrived, and @aakashm has the best answer.

John Weldon
If you make a constructor private in the base class, it flat-out cannot be called from the child class, either explicitly or implicitly.
Steven Sudit
Correct @Steven, but you could make a non-default protected helper constructor that does call the private one.
John Weldon
@Steven correct, and in the above example gives the error that `Person.Person() is inaccessible due to its protection level`
Edward Tanguay
@John: Maybe I'm just not following you. If you're saying that a constructor in the child could chain to a non-default, non-private constructor in the base, which could then chain to the private default constructor in the base, then you are correct. However, I'm not sure how this helps at all here.
Steven Sudit
@Steven; updated my answer to explain what I'm saying, but I think @AakashM has it best.
John Weldon
@John: Thanks for clarifying your answer. I think we're on the same page now.
Steven Sudit
A: 

Your requirement to only need to call the constructor for some derived objects is not in line with the object-oriented principles. If it's a Person, then it needs to be constructed as such.

If you need shared initialization for some objects, then you should consider creating an Initialize method or adding a parameterized constructor that will be called by the specific derived objects.

The parameterized constructor approach feels a little awkward:

public abstract class Person
{
    protected Person()
    {
    }

    protected Person( bool initialize )
    {
        if (initialize)
        {
            Initialize();
        }
    }

    // ...
}

public class Customer : Person
{
    public Customer(string firstName, string lastName)
    {
       // ...
    }
}

public class Employee : Person
{
    public Customer(string firstName, string lastName) : base(true)
    {
       // ...
    }
}
Ryan Emerle
+14  A: 

The only way is to explicitly tell it which other base ctor you want it to call; which of course means you must choose some base ctor to call.

You can't have it call no base ctor at all - conceptually, constructing a Customer is done by first constructing a Person, and then doing Customer specific construction on top of it. For example, suppose Person had private fields - how would these be correctly constructed if construction of a Customer was allowed to not first construct a Person?

AakashM
+1 for directly addressing the fact that, logically, a base constructor *must* be called.
Steven Sudit
I assumed *construction of the class* and *calling the constructor* were two separate actions done by the compiler, for instance, if I put a private field in the base class and step through the construction with the debugger, it *first* defines the private field *then* calls the constructor. I just assumed it defined the private variable and then, only if I explicitly specified that `base()` should called, would it call the base constructor.
Edward Tanguay
so when would I ever use `: base()`, since, the only time the parameter-less base constructor would not be called is if, as you mentioned, I were to specify another constructor to be called, e.g. `: base(firstName, lastName)`, or to put it another way, if you ever see `: base()` you can safely delete it as it is always redundant, right?
Edward Tanguay
Yes, a constructor in the child always calls `base()` unless you tell it to call a non-default base constructor.
Steven Sudit
@Edward Tanguay yes, for example I believe ReSharper will show the use of `: base()` as redundant. For your first point, I personally don't know the *actual* order things are done in (for that, consult Richter / Lippert / Skeet) - I speak only of the OO ideal
AakashM
The base constructor is called before the child constructor.
Steven Sudit
A: 

You could make the default base constructor protected, then have only non-default constructors for the base and its child.

edit

You could give the base a protected constructor with a different signature (such as a protected enum type), and put your initialization code in there, while the default constructor, also protected, does not particular initialization.

Steven Sudit
+3  A: 

In .NET, every object constructor in an object hierarchy will be called regardless if you call :base() or not.

:base() is implicitly called if you don't explicitly call it.

:base() can be used if you want to call a different contructor on a parent object rather than the default constructor.

If you have code in the parent constructor that should not be called everytime, it may be better to move that code to it's own method that will need to be called explicitly after the object is constructed. Or, create a parameterized constructor on the parent object and use the parameter to determine if the code should be executed or not.

For example:

:base(true) - This executes your code.
:base(false) - This does not execute your code.
ChrisNel52
+2  A: 

As others have pointed out, a derived instance must call call one of its base class' constructors.

If you want to control the execution of a base class' initialization logic, remove its default constructor and replace it with something like this:

public class Base {
    // Base has no default constructor
    public Base(bool initialize) {
        if (initialize) {
            // ... logic here 
        }
    }    
}

And the derived constructors look like this:

// Derived1 executes the initialization logic
public Derived1(): base(true) {}

// Derived2 does not
public Derived2(): base(false) {}
Jeff Sternal
You might also consider making the constructor taking an 'initialize' flag protected.
Dan Bryant
A: 

When you instantiate a class all the classes in the inheritance hierarchy are instantiated starting from the topmost Parent to the lowermost child, stopping at the type you instantiated. So if you instantiate System.Text.StringBuilder(), first you call the default constructor of System.Object, then StringBuilder(). You can use the base keyword to call a different constructor (one with equal or fewer params), but not more params. If you do not specify base, the default constructor is called, thus the reason this is happening to you. You can also use the base keyword within the context of an instance method to call base implementations, such as in the case of the decorator pattern. If you do not want to call the base class private constructors then you should set the children with private default constructors and also explicitly call base(params) on your other constructors.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    class ProcessCaller
    {
        static void Main()
        {
            MyDerived md1 = new MyDerived(1);
            object o = new System.Text.StringBuilder();//This will implicitly instantiate the classes in the inheritance hierarchy:  object, then stringbuilder
        }
    }

public class MyBase
{
   int num;

   public MyBase() 
   {
      Console.WriteLine("in MyBase()");
   }

   public MyBase(int i )
   {
      num = i;
      Console.WriteLine("in MyBase(int i)");
   }

   public virtual int GetNum()
   {
      return num;
   }
}

public class MyDerived: MyBase
{
   //set private constructor.  base(i) here makes no sense cause you have no params
   private MyDerived()
   {

   }

    // Without specifying base(i), this constructor would call MyBase.MyBase()
   public MyDerived(int i) : base(i)
   {
   }
   public override int GetNum()
   {
       return base.GetNum();//here we use base within an instance method to call the base class implementation.  
   }
}

}
P.Brian.Mackey
"You can use the base keyword to call a different constructor (one with fewer params), but not more params." Since when?
Steven Sudit
@Steven Sudit Excluding the possibility of hard coding defaults such as Jeff's example base(true).
P.Brian.Mackey
I don't see why we should exclude this possibility. If anything, it's quite common to write a constructor with multiple parameters, then have constructors with fewer parameters that call the main one with defaulted values. This may become less common with VS 2010, thanks to default parameters.
Steven Sudit
As with many devs on SO, you missed the forest for the trees.
P.Brian.Mackey
I can't agree with that. I think it's important that we avoid false implications.
Steven Sudit