Kevin P. Rice
Kevin P. Rice

Reputation: 5753

Force derived class to implement base class constructor with parameter(s)

Is it possible to enforce a compile-time contract on derived classes requiring implementation of a constructor (with parameter)?

I have a base class with a constructor requiring a parameter:

public class FooBase
{
  protected int value;
  public FooBase(int value) { this.value = value; }
  public virtual void DoSomething() { throw new NotImplementedException(); }
}

I'd like to force derivations of my base class to implement the same constructor:

public class Foo : FooBase
{
  public Foo(int value) : base(value) { }
  public override void DoSomething() { Console.WriteLine("Foo: {0}", value); }
}

If no constructor is implemented, derived classes causes a compiler error because there is no default constructor in the base class:

// ERROR: 'Does not contain a constructor that takes 0 arguments'
// Adding default constructor in FooBase eliminates this compiler error, but
// provides a means to instantiate the class without initializing the int value.
public class FooBar : FooBase
{
  public override void DoSomething() { Console.WriteLine("FooBar: {0}", value); }
}

Adding a default constructor, FooBar(), in the derived class silences the compiler error, but provides a dangerous means of instantiating FooBar without the required base class int value being initialized. Because I'm using a factory (see below), silencing the compiler error only results in a run-time error later. I'd like to force FooBar to implement FooBar(int)

INTERESTING OBSERVATION:

If a default constructor, FooBase(), is added to FooBase, then it is 'inherited' by derived classes that do not provide a constructor:

  1. Foo does not inherit the default constructor because it supplies an explicit constructor.
  2. FooBar DOES inherit FooBase().

HOWEVER, the same is not true with the non-default constructor FooBase(int)!

  1. Foo MUST explicitly implement FooBase(int) and call base(int).
  2. FooBar FAILS to 'inherit' the non-default constructor the same way that a default constructor is inherited!

I do not want a default constructor in the base class because instances are created using a factory method that supplies a needed "settings" parameter. That factory method is not illustrated here (which uses the Activator.CreateInstance() method).

Here is the way derived classes should be instantiated:

  static void Main(string[] args)
  {
    FooBase myFoo = new Foo(4);     // Works, since Foo(int) is implemented.

    // ERROR: 'Does not contain a constructor that takes 1 arguments'
    FooBase myFooBar = new FooBar(9);  // Fails to compile.
  }

Because I am using a factory--not direct instantiation as shown--there is no compiler error. Instead, I get a runtime exception: 'Constructor on type not found.'

Unworkable solutions:

It appears that supplying a base class cannot enforce a contract on constructors.

Work-around:

Upvotes: 26

Views: 38737

Answers (5)

jonsequitur
jonsequitur

Reputation: 546

As others have noted, an interface method such as Init() is a bit awkward. An interface should expose behavior, not internal requirements. The internal state of your object helps implement that behavior. A class is generally a way to wrap up state and behavior. The constructor exposes the internal requirements, but the consumer of the interface's "service" doesn't care about this; they only care about the behavior:

interface IFoo 
{
    void DoSomething();
}

So it's natural that different implementations will require different constructors because they often require different internal state to implement IFoo.DoSomething.

The problem that you're running into then is how to write general-purpose code that knows how to instantiate all of these different types. Factory methods and variations on Activator.CreateInstance are commonly used to accomplish this in an ad hoc manner. I'd encourage looking at a couple of alternatives that solve this more elegantly:

  • IoC/DI containers are generally a better approach because they standardize these techniques, perform better, and bring a lot of additional capabilities.
  • In .NET 4, the Managed Extensibility Framework (MEF, a.k.a. System.ComponentModel.Composition) has an overlapping set of capabilities with a lot of the same benefits, especially suited for plugin designs.

Upvotes: 1

svick
svick

Reputation: 245028

If you provide just the constructor with a parameter on the base class, the derived class has to call that when it is constructed. This doesn't however force it how it should be called. It could be called with some default value, or the value could be computed from other constructor parameters.

Constructors are not inherited. What happens instead is that when you don't specify any constructors in a class (structs act differently), a public parameterless constructor is created, that calls the parameterless constructor of the base class. Of course, this won't happen if the base class doesn't have such constructor, in which case you have to specify the constructor yourself.

As @BrokenGlass mentions, the only way to force such a constraint is to have an abstract method Init() on the base class (possibly from an interface). I think that in general, such practice is not good OOP-design (object should be usable after creation, without the need to call other methods), but in this case it might be the best solution.

Upvotes: 3

Lu4
Lu4

Reputation: 15030

It seems either that I don't understand what you mean or what you mean is wrong.

Having the following, causes compiler error:

public class FooBase
{
    protected int value;
    public FooBase(int value) { this.value = value; }
    public virtual void DoSomething() { throw new NotImplementedException(); }
}
public class Foo : FooBase
{
    public Foo(int value) : base(value) { }
    public override void DoSomething() { Console.WriteLine("Foo: {0}", value); }
}

public class FooBar : FooBase
{
    public FooBar() // <----------------- HERE telling 'Test.FooBase' does not contain a constructor that takes 0 arguments
    {
    }

    public override void DoSomething() { Console.WriteLine("FooBar: {0}", value); }
}

So it is safe. But if you try to do the following

public class FooBase
{
    protected int value;
    public FooBase() {} // <------------ LOOK HERE
    public FooBase(int value) { this.value = value; }
    public virtual void DoSomething() { throw new NotImplementedException(); }
}
public class Foo : FooBase
{
    public Foo(int value) : base(value) { }
    public override void DoSomething() { Console.WriteLine("Foo: {0}", value); }
}

public class FooBar : FooBase
{
    public FooBar() // <----------------- No error here
    {
    }

    public override void DoSomething() { Console.WriteLine("FooBar: {0}", value); }
}

And if it is wrong to declare ctor in FooBase then it's your responsibility as a developer not to do so...

Upvotes: 2

BrokenGlass
BrokenGlass

Reputation: 161002

If a default constructor, FooBase(), is added to FooBase, then it is 'inherited' by derived classes that do not provide a constructor:

This is incorrect - constructors in general are never inherited. A default constructor is automatically provided for a class that does not provide any other constructor implementation.

You could put in a constraint on an interface that provides an Init() method for you:

public interface IInit
{
   void Init(int someValue);
}

public class FooBase : IInit
{
   ..
}

Upvotes: 12

Ahmed Magdy
Ahmed Magdy

Reputation: 6050

Did you try

public class FooBase
{
  protected int value;
  private FooBase(){}
  public FooBase(int value) { this.value = value; }
  public virtual void DoSomething() { throw new NotImplementedException(); }
}

the private constructor prevents the option of parameter-less constructor

Upvotes: 10

Related Questions