是否可以对需要实现构造函数(带参数)的派生类强制执行编译时合同?
我有一个带有构造函数的基类,需要一个参数:
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); }
}
如果没有实现构造函数,派生类会导致编译器错误,因为基类中没有默认构造函数:
// 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); }
}
在派生类中添加默认构造函数FooBar()会使编译器错误无效,但是提供了一种实例化FooBar的危险方法,而不需要初始化所需的基类int值。因为我正在使用工厂(见下文),所以静默编译器错误只会导致稍后的运行时错误。我想强制FooBar实现FooBar(int)
有趣的观察:
如果将默认构造函数FooBase()添加到FooBase,那么它将由不提供构造函数的派生类“继承”:
然而,非默认构造函数FooBase(int)也是如此!
我不想在基类中使用默认构造函数,因为实例是使用提供所需“settings”参数的工厂方法创建的。此处未说明该工厂方法(使用Activator.CreateInstance()方法)。
以下是派生类应该实例化的方式:
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.
}
因为我正在使用工厂 - 而不是如图所示的直接实例化 - 所以没有编译器错误。相反,我得到一个运行时异常:'找不到类型的构造函数。'
不可行的解决方案:
似乎提供基类不能强制构造函数的合同。
解决方法:
答案 0 :(得分:10)
你试过吗
public class FooBase
{
protected int value;
private FooBase(){}
public FooBase(int value) { this.value = value; }
public virtual void DoSomething() { throw new NotImplementedException(); }
}
私有构造函数阻止了无参数构造函数
的选项答案 1 :(得分:9)
如果是默认构造函数,FooBase(), 添加到FooBase,然后它 由派生类'继承' 不提供构造函数:
这是不正确的 - 一般来说构造函数永远不会被继承。为不提供任何其他构造函数实现的类自动提供默认构造函数。
您可以在为您提供Init()方法的接口上设置约束:
public interface IInit
{
void Init(int someValue);
}
public class FooBase : IInit
{
..
}
答案 2 :(得分:2)
似乎我不明白你的意思或你的意思是错的。
具有以下内容会导致编译器错误:
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); }
}
所以这很安全。但是,如果您尝试执行以下操作
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); }
}
如果在FooBase中声明ctor是错误的,那么作为开发人员,你有责任不这样做......
答案 3 :(得分:2)
如果只为构造函数提供基类的参数,派生类必须在构造时调用它。然而,这并不强制它应该如何被调用。可以使用某个默认值调用它,或者可以从其他构造函数参数计算该值。
构造函数不是继承的。相反,当你没有在类中指定任何构造函数(结构的行为不同)时,会创建一个公共无参数构造函数,它调用基类的无参数构造函数。当然,如果基类没有这样的构造函数,就不会发生这种情况,在这种情况下你必须自己指定构造函数。
正如@BrokenGlass所提到的,强制执行此类约束的唯一方法是在基类(可能来自接口)上使用抽象方法Init()
。我认为一般来说,这种做法不是好的OOP设计(对象应该在创建之后可用,而不需要调用其他方法),但在这种情况下它可能是最好的解决方案。
答案 4 :(得分:1)
正如其他人所说,接口方法如Init()有点尴尬。接口应该暴露行为,而不是内部要求。对象的内部状态有助于实现该行为。类通常是一种包含状态和行为的方法。构造函数暴露了内部需求,但接口的“服务”的使用者并不关心这一点;他们只关心这种行为:
interface IFoo
{
void DoSomething();
}
因此,不同的实现需要不同的构造函数是很自然的,因为它们通常需要不同的内部状态来实现IFoo.DoSomething。
您遇到的问题是如何编写知道如何实例化所有这些不同类型的通用代码。 Activator.CreateInstance的工厂方法和变体通常用于以临时方式实现此目的。我鼓励我们考虑一些更优雅的解决方案: