Как наследовать конструкторы? - PullRequest
181 голосов
/ 21 октября 2008

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

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?

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

Ответы [ 14 ]

122 голосов
/ 21 октября 2008

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

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

Обновление

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

70 голосов
/ 21 октября 2008

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

public Foo(params int[] list) {...}
42 голосов
/ 21 октября 2008

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

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

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

34 голосов
/ 11 апреля 2009

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

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

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

27 голосов
/ 22 октября 2008

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

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^
10 голосов
/ 16 ноября 2011

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

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) {}
}

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

7 голосов
/ 22 октября 2008

Как класс 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() {...}
   ...
}

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

6 голосов
/ 30 сентября 2010
public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

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

    }
}
5 голосов
/ 22 июля 2011

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

Таким образом, мы просто переопределяем (с более низкой видимостью - то есть частным) конструкторы, которые нам НЕ НУЖНЫ, вместо того, чтобы добавлять все конструкторы, которые мы ДЕЛАЕМ. Делфи делает это таким образом, и я скучаю по нему.

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

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

4 голосов
/ 14 июля 2014

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

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

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

Нет.

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

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

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

Это выглядит так:

// 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 напрямую, но, конечно, это может быть какой-то фабричный класс, если информация, которую он настраивает, может быть установлена ​​этим фабричным классом.

Просто для ясности: имя 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);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...