Вопрос

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

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?

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

Это было полезно?

Решение

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

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

Update

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 с использованием переопределенной видимости.

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

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

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

Неа.

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

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

Но вот в 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);
        }
    }
}

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top