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

#c# #inheritance #constructor

#c# #наследование #конструктор

Вопрос:

Я видел много вариантов этого вопроса, заданного на SO, но решения в этом случае неприменимы.

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

 abstract class Base
{
    private int ID;

    protected Base( int id )
    {
        ID = id;
    }

    protected Base()
    {
        ID = GenerateID();
    }

    private int GenerateID()
    {
        return 42;
    }
}

class Derived : Base
{
    public readonly int SomeField;
    
    public Derived( int SomeFieldInitialValue ) : base()
    {
        SomeField = SomeFieldInitialValue;
    }

    public Derived( int SomeFieldInitialValue, int id ) : base( id )
    {
        // Want to remove this duplication - could be a long constructor.
        // Can't put it in a method because it does things that only constructors can do.
        SomeField = SomeFieldInitialValue;
    }
}
  

Я не могу создать вспомогательный метод, Derived содержащий код, общий для всех конструкторов, поскольку часть этого кода может быть выполнена только в конструкторах (например, установка членов только для чтения).

Я не могу использовать только один базовый конструктор и передавать значения по умолчанию, поскольку значение, которое будет использоваться для Base.ID , известно только Base (вычисляется в частном GenerateID методе).

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

Я бы предпочел не предоставлять Base.GenerateID производным классам возможность передачи в отсутствие идентификатора для передачи, поскольку нет другой причины для его раскрытия, и кажется грязным перенос базовой функциональности в производные классы. Также в будущем, вероятно, появятся другие детали, помимо простого int — есть всевозможный другой код, который может потребоваться или не потребоваться для запуска в зависимости от того, какой Base конструктор вызывается — предоставление Derived информации об этом кажется плохим.

Я бы предпочел не удалять члены readonly from Derived , поскольку они readonly предназначены специально.

У меня такое чувство, что ответ будет «извините, C # этого не позволяет», и мне придется выбирать между одним из вариантов, которые я не хочу принимать 🙂 Я думаю, что наименее плохой вариант — удалить readonly или, возможно, заменить эти поля private set свойствами.

Комментарии:

1. Чтобы добавить один вариант: определите базовый c’torкак Base(int? id) , вызывая GenerateId, если параметр равен null . Кроме того, определите private Derived(int SomeFieldInitialValue, int? id) выполнение всей работы и перенаправление с общедоступных c’торов на этот новый.

2. Это слишком абстрактный вопрос. Можете ли вы реорганизовать базовый класс, чтобы там был только один конструктор? Например. используйте фабричный метод для создания экземпляра с сгенерированным идентификатором. Затем вы можете связать производные конструкторы классов, вызвав this(...) вместо base(...) , это сохранит всю инициализацию в одном месте.

3. Согласен с Клаусом — поскольку все базовые конструкторы защищены, а класс абстрактен — объединение их в один (принятие int? ) не повредит «общедоступному» использованию (не сделает его менее удобным), при этом устраняя вашу проблему с унаследованными классами.

Ответ №1:

Если вы можете изменить базовый класс, было бы лучше иметь там только один конструктор. Затем это может предоставить два варианта (фиксированный идентификатор или вновь сгенерированный идентификатор) следующим образом:

 abstract class Base
{
    private int ID;

    protected Base( int? id )
    {
        ID = id ?? GenerateID();
    }

    private int GenerateID()
    {
        return 42;
    }
}
  

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

 class Derived : Base
{
    public readonly int SomeField;
    
    protected Derived( int SomeFieldInitialValue, int? id ) : base(id)
    {
        SomeField = SomeFieldInitialValue;
    }
    
    public Derived( int SomeFieldInitialValue ) : this( SomeFieldInitialValue, null)
    {
    }

    public Derived( int SomeFieldInitialValue, int id ) : this( SomeFieldInitialValue, id )
    {
    }
}
  

Комментарии:

1. Ох. Мне это действительно нравится. Спасибо! У меня есть малейшее беспокойство по поводу того, что это менее эффективно, чем прямой вызов разных конструкторов (я работаю в разработке игр, поэтому эффективность является огромным приоритетом), но создание объектов (и вызов конструкторов) уже признано медленным, поэтому замедление работы конструкторов не должно иметь значения 🙂