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

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

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?

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
183
0
185 696
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

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

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

Обновлять

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

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

Ian Boyd 21.10.2008 23:51

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

Jeff Yates 21.10.2008 23:59

Невидимые конструкторы базовых классов являются результатом выбора, сделанного разработчиками языка C# (и, вполне возможно, других языков CLR). Как ни печально, это не имеет ничего общего с предварительным дизайном приложения, но с ограничениями языка, которые делают дизайн со многими конструкторами в базовом классе (часто) непрактичным.

too 21.08.2012 20:02

Я не согласен. Предварительный дизайн по-прежнему остается ответственным, независимо от языковых ограничений. Вы должны разрабатывать дизайн для платформы, на которой вы реализуете, и языка, который вы используете.

Jeff Yates 21.08.2012 20:09

В этом контексте вы правы, хотя теперь «лучшие практики» становятся «лучшими доступными практиками».

too 26.08.2012 22:06

Больше всего меня раздражает кастомное исключение - Sub New(), Sub New(Message As String), Sub New(Message As String, InnerEx as Exception), Sub New(Info As Serialization.SerializationInfo, Context As Serialization.StreamingContext) ... Yawn `

Basic 20.05.2013 23:31

@Basic: Возможно, это раздражает (да, это так), но вы не имеют, чтобы реализовать их все. Только те, которые вы планируете использовать.

Matt Burland 26.03.2014 22:11

@MattBurland Это зависит от того, будут ли ваши исключения видны другим кодом. Если это так, вам необходимо охватить их все на случай, если он попытается сериализовать / создать экземпляр и сделать предположения. Признаюсь, я делаю это только для того, чтобы код казался «правильным», и, по крайней мере, в некоторых случаях это не требуется.

Basic 26.03.2014 23:29
«Укажите значения параметров по умолчанию и используйте именованные параметры, чтобы один конструктор поддерживал конфигурации с несколькими аргументами» Не могли бы вы привести простой пример этого? Я не совсем понимаю, какие части этого идут в родительском классе, а что - в дочернем, и как они работают вместе.
Slipp D. Thompson 24.09.2014 23:57

Это одна из областей, где новый C++ действительно выделяется на фоне более простых языков :)

Mark K Cowan 21.12.2016 16:41

Да, с. C++ 11 поддерживает параметр компилятора для автоматического наследования конструкторов вместо того, чтобы заставлять нас вводить их пустые копии. Давай, C#! Это ужасная проблема для обновления подклассов в нескольких доменах каждый раз, когда нам нужно добавить новый необязательный параметр.

Triynko 10.08.2018 22:57

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

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

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

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

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

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

Marcus King 21.10.2008 23:07

Если честно, я сам никогда не использовал их ни в каком производственном коде. У меня обычно есть List <int> или какая-то коллекция, которую я передаю. Я с нетерпением жду того дня, когда смогу использовать параметры. :)

Kon 21.10.2008 23:12

Изменится ли ваш ответ, если будет только один конструктор?

Ian Boyd 27.10.2008 16:58

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

annakata 20.02.2009 15:46

Это действительно работает только в том случае, если параметры одного типа и используются как список для инициализации объекта. Но если бы у конструкторов была даже другая сигнатура (скажем, один принимает два int, а другой две строки, а другой и int и строку, а четвертый, float и enum. Вы бы потратили большую часть конструктора на проверку типов и unbox - делать во время выполнения вещи, которые действительно должны выполняться компилятором.

James Curran 04.05.2016 22:54

Единственный конструктор по-прежнему должен быть скопирован во все производные классы, и это отстой.

Dennis Gorelik 29.06.2017 11:22

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

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

trampster 22.10.2008 02:00

@Daniel, поэтому я предложил фабричный паттерн с использованием внутренних сеттеров. Таким образом, установщики доступны только классу, который создает объект, и объект может поддерживать (для всех намерений и целей) свою неизменность.

tvanfosson 22.10.2008 02:07

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

Не отвечая на настоящий вопрос. И не помогает, если классу требуется неизменяемое состояние.

trampster 22.10.2008 02:09

Отсюда «если у вас нет уважительной причины». Дело в том, что любой класс с 387 конструкторами делает слишком много.

Chris Marasti-Georg 22.10.2008 02:14

Вопрос не меняется, если у меня 2 конструктора; как решить проблему копирования и вставки двух строк.

Ian Boyd 11.06.2012 17:52

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

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

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

Вы не должны вызывать виртуальные методы в конструкторах - подкласс не будет запускать свой конструктор во время вызова виртуального метода.

Chris Marasti-Georg 21.10.2008 23:27

Да, наверное, хороший совет. Однако у вас может быть виртуальный шаблон инициализации, вызываемый вызывающими объектами после создания. Паттерн будет таков: Bar bar = new Bar (); bar.Initialize ();

C. Dragon 76 22.10.2008 01:42

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

Звучит интересно, хотя я не знаю, что такое ковариантность. :(

Ian Boyd 27.10.2008 17:00

По сути, это означает, что вы можете переопределить функцию с теми же параметрами, изменив ее тип возвращаемого значения, поэтому «string Function (int, int)» может отличаться от «int Function (int, int)». Если вы попробуете это сейчас на C#, он выдает сообщение о том, что перегрузка уже существует.

user593806 04.01.2012 15:31

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

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

Поскольку 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() {...}
   ...
}

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

Да, это очень хорошая идея; хотя я бы, вероятно, инициализировал статический метод, который возвращает класс

Ian Boyd 27.10.2008 17:01

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

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

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

Resharper упрощает это! (Alt + Insert, конструкторы)

Callum Rogers 04.05.2010 23:52
public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

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

    }
}

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

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

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

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

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

supercat 14.01.2014 03:35

Судя по всему, эта опция компилятора существует в C++ 11, но C# отстает.

Triynko 10.08.2018 22:59

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

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, но иногда вам просто нужен умный конструктор. Просто мысль.

Do i really have to copy all constructors from Foo into Bar and Bah? And then if i change a constructor signature in Foo, do i have to update it in Bar and Bah?

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

Is there no way to inherit constructors?

Неа.

Is there no way to encourage code reuse?

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

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

Выглядит это так:

// 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);
        }
    }
}

Я как раз собирался опубликовать решение для функции фабрики шаблонов ... Но потом вы опередили меня на 2,5 года

Mark K Cowan 21.12.2016 16:43

Другие вопросы по теме