Reputation: 5753
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:
HOWEVER, the same is not true with the non-default constructor FooBase(int)!
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
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:
Upvotes: 1
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
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
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
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