-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Problem
This is a proposal for making it easier to derive base classes with multiple constructors.
The problem occurs when the derived class wants to extend or replace the argument list of multiple base constructors while still doing the exact same initialization internally. Consider the following code
namespace Ctor
{
class NumberSuffix
{
private readonly string text;
public NumberSuffix(byte b) { text = b.ToString(); }
public NumberSuffix(sbyte sb) { text = sb.ToString(); }
public NumberSuffix(short s) { text = s.ToString(); }
public NumberSuffix(ushort us) { text = us.ToString(); }
// And so on...
}
class PrefixText : NumberSuffix
{
private readonly string preText;
public PrefixText(string prefixText, byte b) : base(b) { preText = prefixText; }
public PrefixText(string prefixText, sbyte sb) : base(sb) { preText = prefixText; }
public PrefixText(string prefixText, ushort us) : base(us) { preText = prefixText; }
public PrefixText(string prefixText, short s) : base(s) { preText = prefixText; }
// And so on...
}
}As we can see, the derived class PrefixText performs the same initialization in all its constructors, but calls a different base constructor each time.
This is a pretty silly and very simplified example which in this case could be solved using generics. However, consider the case where a class has constructors with argument lists of different lengths (and different types at one or more common positions).
Proposal
Code duplication for the example implementation of the derived class PrefixText could be avoided using the following syntax:
class PrefixText : NumberText
{
private readonly string preText;
private PrefixText(string preText) { this.preText = preText; }
public PrefixText(string preText, byte b) : base(b), this(preText) { }
public PrefixText(string preText, byte sb) : base(sb), this(preText) { }
public PrefixText(string preText, byte s) : base(s), this(preText) { }
public PrefixText(string preText, byte us) : base(us), this(preText) { }
}This suggests a comma-separated constructor call list. This would have to have the following restrictions:
- Multiple constructors are called in the order they are specified.
- If a base constructor is specified, the first constructor must be the base constructor. This must also be the only base-constructor, as there would not be an easy way to check for circular dependencies in the base constructor implementation which might be located in a different assembly.
Gain
In the shown example the gain of such a language feature is minimal. The feature above could for example easily be implemented using Generics. However, this does prevent the derivation from duplicating possibly complex initialization code. Complex initializations could be put into a common method, but that would still require the developer to write identical bodies for a potentially large number of constructors (which would introduce a potential bug-source). A common initialization method would also not be able to set readonly field and properties (as shown in the example above).
Implementation
In theory the compiler could be made to check the constructor call graph to avoid circular dependencies or calling one constructor multiple times. This might be difficult for the base class constructors, as they might be implemented in different assemblies where the call graphs might not easily be available.
As a simplification one could start with only accepting two constructors where the second (this) constructor may not have an additional constructor call dependency.