Принудительный класс для реализации конструктора базового класса с параметрами (параметрами)
Можно ли принудительно выполнить контракт на компиляцию на производных классах, требующих реализации конструктора (с параметром)?
У меня есть базовый класс с конструктором, требующим параметр:
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 без инициализации инициализированного значения инициализации базового класса. Поскольку я использую factory (см. Ниже), глушение ошибки компилятора приводит только к ошибке во время выполнения. Я хотел бы заставить FooBar реализовать FooBar (int)
ИНТЕРЕСНОЕ НАБЛЮДЕНИЕ:
Если конструктор по умолчанию FooBase() добавлен в FooBase, он наследуется производными классами, которые не предоставляют конструктор:
- Foo не наследует конструктор по умолчанию, поскольку он предоставляет явный конструктор.
- FooBar наследует FooBase().
ОДНАКО, то же самое не относится к конструктору non-default FooBase (int)!
- Foo ДОЛЖЕН явно реализовать FooBase (int) и базу вызовов (int).
- FooBar FAILS наследует конструктор не по умолчанию так же, как наследуется конструктор по умолчанию!
Мне не нужен конструктор по умолчанию в базовом классе, потому что экземпляры создаются с использованием метода factory, который предоставляет необходимый параметр "settings". Этот метод factory здесь не проиллюстрирован (который использует метод 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.
}
Потому что я использую factory - не прямое инстанцирование, как показано - ошибки компилятора нет. Вместо этого я получаю исключение во время выполнения: "Конструктор по типу не найден."
Нерабочие решения:
- Интерфейсы не поддерживают конструкторы.
- Конструкторы не могут быть виртуальными или абстрактными.
Похоже, что предоставление базового класса не может привести к заключению контракта с конструкторами.
Работа вокруг:
- Предоставить конструктор по умолчанию в базовом классе вместе с свойством для параметра параметров передачи.
Ответы
Ответ 1
Если конструктор по умолчанию, FooBase(), добавляется в FooBase, то это "наследуется" производными классами, которые делают не предоставлять конструктор:
Это неверно - конструкторы вообще никогда не наследуются. Конструктор по умолчанию автоматически предоставляется для класса, который не предоставляет никакой другой реализации конструктора.
Вы можете установить ограничение на интерфейс, который предоставляет вам метод Init():
public interface IInit
{
void Init(int someValue);
}
public class FooBase : IInit
{
..
}
Ответ 2
Вы пробовали
public class FooBase
{
protected int value;
private FooBase(){}
public FooBase(int value) { this.value = value; }
public virtual void DoSomething() { throw new NotImplementedException(); }
}
частный конструктор предотвращает возможность конструктора без параметров
Ответ 3
Кажется, что я не понимаю, что вы имеете в виду или что вы имеете в виду, это неправильно.
Имея следующее, вызывает ошибку компилятора:
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); }
}
И если неправильно объявлять ctor в FooBase, тогда ваша ответственность как разработчика не делать этого...
Ответ 4
Если вы предоставляете только конструктор с параметром в базовом классе, производный класс должен вызывать это при его построении. Однако это не заставляет его, как его следует называть. Его можно вызвать с некоторым значением по умолчанию, или значение может быть вычислено из других параметров конструктора.
Конструкторы не наследуются. Вместо этого происходит то, что когда вы не укажете каких-либо конструкторов в классе (structs act differently), создается открытый конструктор без параметров, который вызывает конструктор без параметров без базового класса. Конечно, этого не произойдет, если базовый класс не имеет такого конструктора, и в этом случае вы должны сами указать конструктор.
Как упоминает @BrokenGlass, единственный способ заставить такое ограничение - иметь абстрактный метод Init()
в базовом классе (возможно, из интерфейса). Я думаю, что в целом такая практика не является хорошим ООП-дизайном (объект должен быть полезен после создания, без необходимости вызова других методов), но в этом случае это может быть лучшим решением.
Ответ 5
Как отмечали другие, метод интерфейса, такой как Init(), немного неудобен. Интерфейс должен выставлять поведение, а не внутренние требования. Внутреннее состояние вашего объекта помогает реализовать это поведение. Класс обычно является способом обертывания состояния и поведения. Конструктор предоставляет внутренние требования, но потребителю интерфейса "услуга" это не заботит; они только заботятся о поведении:
interface IFoo
{
void DoSomething();
}
Итак, естественно, что для разных реализаций потребуются разные конструкторы, потому что они часто требуют другого внутреннего состояния для реализации IFoo.DoSomething.
Проблема, с которой вы столкнулись, заключается в том, как написать код общего назначения, который знает, как создавать все эти разные типы. Factory методы и вариации на Activator.CreateInstance обычно используются для выполнения этого специальным образом. Я бы посоветовал взглянуть на пару альтернатив, которые решают это более элегантно:
- Контейнеры IoC/DI, как правило, лучше подходят, потому что они стандартизируют эти методы, работают лучше и приносят много дополнительных возможностей.
- В .NET 4 Framework Managed Extensibility Framework (MEF, a.k.a. System.ComponentModel.Composition) имеет перекрывающийся набор возможностей с множеством одинаковых преимуществ, особенно подходящих для проектов плагинов.