Как наследовать конструкторы?

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

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

и теперь я хочу создать класс потомка, который переопределяет виртуальный метод:

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}

И еще один потомок, который делает еще кое-что:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

Нужно ли мне копировать все конструкторы из Foo в Bar и Bah? И тогда, если я изменяю подпись конструктора в Foo, мне нужно обновить ее в Bar и Bah?

Нет способа унаследовать конструкторы? Нет ли способа поощрения повторного использования кода?

Ответ 1

Да, вам нужно будет реализовать конструкторы, которые имеют смысл для каждого вывода, а затем использовать ключевое слово base, чтобы направить этот конструктор на соответствующий базовый класс или ключевое слово this, чтобы направить конструктор на другой конструктор в такой же класс.

Если компилятор сделал предположения о наследовании конструкторов, мы не смогли бы правильно определить, как были созданы экземпляры наших объектов. В большинстве случаев вам следует подумать о том, почему у вас так много конструкторов, и подумайте о том, чтобы сократить их до одного или двух в базовом классе. Затем производные классы могут маскировать некоторые из них, используя постоянные значения, такие как null и только выставлять необходимые их через свои конструкторы.

Update

В С# 4 вы можете указать значения параметров по умолчанию и использовать именованные параметры, чтобы один конструктор поддерживал несколько конфигураций аргументов, а не один конструктор для каждой конфигурации.

Ответ 2

387 конструкторов?? Это ваша главная проблема. Как насчет этого?

public Foo(params int[] list) {...}

Ответ 3

Да, вам нужно скопировать все 387 конструкторов. Вы можете сделать повторное использование, перенаправив их:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}

но это лучшее, что вы можете сделать.

Ответ 4

Слишком плохо, мы как бы вынуждены сообщать компилятору очевидное:

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

Мне нужно всего лишь 3 конструктора в 12 подклассах, так что это не имеет большого значения, но я не слишком люблю повторять это на каждом подклассе, после того как его использовали, чтобы не писать так долго. Я уверен, что для этого есть веская причина, но я не думаю, что когда-либо сталкивался с проблемой, требующей такого ограничения.

Ответ 5

Не забывайте, что вы также можете перенаправить конструкторы на другие конструкторы на том же уровне наследования:

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^

Ответ 6

Другим простым решением может быть использование структуры или простого класса данных, который содержит параметры как свойства; таким образом, вы можете заранее установить все значения и поведение по умолчанию, передав "класс параметров" в качестве единственного параметра конструктора:

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

Это позволяет избежать беспорядка нескольких конструкторов (иногда) и дает сильную типизацию, значения по умолчанию и другие преимущества, не предоставляемые массивом параметров. Это также облегчает перенос, поскольку все, что наследуется от Foo, может по-прежнему добираться до FooParams или даже добавлять к ним по мере необходимости. Вам все равно нужно скопировать конструктор, но вы всегда (чаще всего) только (как общее правило) когда-либо (по крайней мере, на данный момент) нуждаетесь в одном конструкторе.

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

Мне очень нравится, что перегруженные шаблоны Initialailize() и Class Factory подходят лучше, но иногда вам просто нужно иметь интеллектуальный конструктор. Просто мысль.

Ответ 7

Поскольку Foo - это класс, вы не можете создавать виртуальные перегруженные методы Initialise()? Тогда они будут доступны для подклассов и все еще расширяемы?

public class Foo
{
   ...
   public Foo() {...}

   public virtual void Initialise(int i) {...}
   public virtual void Initialise(int i, int i) {...}
   public virtual void Initialise(int i, int i, int i) {...}
   ... 
   public virtual void Initialise(int i, int i, ..., int i) {...}

   ...

   public virtual void SomethingElse() {...}
   ...
}

Это не должно иметь более высокую стоимость исполнения, если у вас много значений свойств по умолчанию, и вы сильно ударили по нему.

Ответ 8

public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

public class ChildClass : BaseClass
{
    public ChildClass(params int[] parameters)
        : base(parameters)
    {

    }
}

Ответ 9

Лично я считаю, что это ошибка в части Microsoft, они должны были позволить программисту переопределить видимость конструкторов, методов и свойств в базовых классах, а затем сделать так, чтобы конструкторы всегда наследовались.

Таким образом, мы просто просто переопределяем (с более низкой видимостью - т.е. Private) конструкторы, которые мы НЕ хотим, вместо того, чтобы добавлять все конструкторы, которые мы хотим. Delphi делает это так, и я скучаю по нему.

Возьмем, к примеру, если вы хотите переопределить класс System.IO.StreamWriter, вам нужно добавить все 7 конструкторов в ваш новый класс, и если вам нравится комментирование, вам нужно прокомментировать каждый из них с заголовком XML. Чтобы ухудшить ситуацию, представление метаданных dosnt помещает комментарии XML в качестве правильных комментариев XML, поэтому нам нужно идти по очереди, копировать и вставлять их. О чем подумала Microsoft?

Я на самом деле написал небольшую утилиту, в которую вы можете вставить код метаданных, и он преобразует ее в комментарии XML, используя скрытую видимость.

Ответ 10

Проблема не в том, что Bar и Bah должны копировать 387 конструкторов, проблема в том, что Foo имеет 387 конструкторов. Foo явно делает слишком много вещей - рефакторинг быстро! Кроме того, если у вас нет веской причины иметь значения, установленные в конструкторе (который, если вы предоставите конструктор без параметров, вы, вероятно, этого не сделаете), я бы рекомендовал использовать свойство get/setting.

Ответ 11

Нужно ли мне копировать все конструкторы из Foo в Bar и Bah? И тогда, если я изменяю подпись конструктора в Foo, мне нужно обновить его в Bar и Bah?

Да, если вы используете конструкторы для создания экземпляров.

Нет ли способа наследования конструкторов?

Неа.

Нет ли способа поощрения повторного использования кода?

Ну, я не буду разбираться в том, будут ли наследовательные конструкторы хорошими или плохими и будет ли это поощрять повторное использование кода, поскольку у нас их нет, и мы их не получим.: -)

Но здесь, в 2014 году, с текущим С# вы можете получить нечто похожее на наследуемые конструкторы, используя вместо этого общий метод create. Это может быть полезным инструментом для вашего пояса, но вы не достигнете его легкомысленно. Недавно я потянулся к нему, столкнувшись с необходимостью передать что-то в конструктор базового типа, используемого в нескольких сотнях производных классов (до недавнего времени базе не нужны никакие аргументы, и поэтому конструктор по умолчанию был fine — производные классы вообще не объявляли конструкторы и получали автоматически поставляемый).

Он выглядит следующим образом:

// In Foo:
public T create<T>(int i) where: where T : Foo, new() {
    T obj = new T();
    // Do whatever you would do with `i` in `Foo(i)` here, for instance,
    // if you save it as a data member;  `obj.dataMember = i;`
    return obj;
}

Это говорит о том, что вы можете вызвать общую функцию create, используя параметр типа, который является любым подтипом Foo, который имеет конструктор с нулевыми аргументами.

Затем вместо выполнения Bar b new Bar(42) вы сделаете следующее:

var b = Foo.create<Bar>(42);
// or
Bar b = Foo.create<Bar>(42);
// or
var b = Bar.create<Bar>(42); // But you still need the <Bar> bit
// or
Bar b = Bar.create<Bar>(42);

Там я показал метод create, находящийся непосредственно на Foo, но, конечно, он может быть в классе factory какого-либо рода, если информация, которую он устанавливает, может быть задана этим factory класс.

Просто для ясности: имя create не важно, это может быть makeThingy или что-то еще, что вам нравится.

Полный пример

using System.IO;
using System;

class Program
{
    static void Main()
    {
        Bar b1 = Foo.create<Bar>(42);
        b1.ShowDataMember("b1");

        Bar b2 = Bar.create<Bar>(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter
        b2.ShowDataMember("b2");
    }

    class Foo
    {
        public int DataMember { get; private set; }

        public static T create<T>(int i) where T: Foo, new()
        {
            T obj = new T();
            obj.DataMember = i;
            return obj;
        }
    }

    class Bar : Foo
    {
        public void ShowDataMember(string prefix)
        {
            Console.WriteLine(prefix + ".DataMember = " + this.DataMember);
        }
    }
}

Ответ 12

Нет, вам не нужно копировать все 387 конструкторов в Bar и Bah. Bar и Bah могут иметь столько или несколько конструкторов, сколько хотите, независимо от того, сколько вы определяете на Foo. Например, вы можете выбрать только один конструктор Bar, который создает Foo с конструктором Foo 212th.

Да, любые конструкторы, которые вы изменяете в Foo, на которых работают Bar или Bah, потребуют от вас изменить Bar и Bah соответственно.

Нет, в .NET нет возможности наследовать конструкторы. Но вы можете добиться повторного использования кода, вызвав конструктор базового класса внутри конструктора подкласса или вызвав виртуальный метод, который вы определяете (например, Initialize()).

Ответ 13

Возможно, вы сможете адаптировать версию Иконки виртуального конструктора С++. Насколько я знаю, С# не поддерживает ковариантные типы возврата. Я считаю, что в списках пожеланий многих народов.

Ответ 14

Слишком много конструкторов является признаком сломанной конструкции. Лучше класс с несколькими конструкторами и возможностью устанавливать свойства. Если вам действительно нужен контроль над свойствами, рассмотрите factory в том же пространстве имен и сделайте внутренние сетки свойств. Пусть factory решает, как создать экземпляр класса и установить его свойства. factory может иметь методы, которые принимают как можно больше параметров для правильной настройки объекта.