Принудительный класс для реализации конструктора базового класса с параметрами (параметрами)

Можно ли принудительно выполнить контракт на компиляцию на производных классах, требующих реализации конструктора (с параметром)?

У меня есть базовый класс с конструктором, требующим параметр:

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) имеет перекрывающийся набор возможностей с множеством одинаковых преимуществ, особенно подходящих для проектов плагинов.