Абстрактные обобщенные классы, принимающие параметры типа, которые сами являются производными от этого класса - PullRequest
5 голосов
/ 26 июля 2010

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

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

Если «плохо», какую альтернативу вы предпочитаете обрабатывать в таких ситуациях и как бы вы структурировали приведенный ниже код?

Например: -

    // We pass both the wrapped class and the wrapping class as type parameters 
    // to the generic class allowing it to create instances of either as necessary.

    public abstract class CoolClass<T, U>
        where U : CoolClass<T, U>, new()
    {
        public T Value { get; private set; }
        protected CoolClass() { }
        public CoolClass(T value) { Value = value; }
        public static implicit operator CoolClass<T, U>(T val)
        {
            // since we know the derived type and that its new(), we can
            // new up an instance of it (which we couldn't do as an abstract class)
            return new U() { Value = val};
        }
        public static implicit operator T(CoolClass<T, U> obj)
        {
            return obj.Value;
        }
    }

И второй бонусный вопрос: почему ОДИН из этих неявных операторов работает, а другой нет?

Например,

    public class CoolInt : CoolClass<int, CoolInt>
    {
        public CoolInt() {  }
        public CoolInt(int val) (val) { }
    }

                                    // Why does this not work
        CoolInt x = 5;
                                    // when this works
        CoolInt x2 = (CoolInt)5;    
                                    // and this works
        int j = x;

Ответы [ 2 ]

1 голос
/ 26 июля 2010

Это распространенный (и хороший!) Шаблон в C ++, который выглядит примерно так:

template<typename T> class Base {
    void foo() { T::foo(); /* Call the derived implementation*/ }
};
class Derived : public Base<Derived> {
    void foo() { /* do something*/ }
};

Мы используем его для статического полиморфизма / наследования, наряду с другими.

Однако в .NET, где общие параметры находятся во время выполнения и также есть отражение, я не совсем уверен, в чем выгода. Я имею в виду, что наличие производного типа полезно, но вы должны спросить себя - полезно для чего и чем оно отличается от прямого наследования?

1 голос
/ 26 июля 2010

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

почему ОДИН из этих неявных операторов работает, а другой нет?

Поскольку вы определили преобразование из CoolClass<T, U>, но не из CoolInt.Они разных типов.Это будет работать, если у вас есть этот метод в вашей реализации CoolInt:

public static implicit operator CoolInt(int val)

Об использовании обобщений :

Такое использование обобщений создает ограничения для вашей архитектуры, если выпотребуется создать сложные иерархии наследования со многими классами (например, введение нового уровня абстракции может быть сложным).Но это действительно зависит от того, что вам нужно.Я фактически использовал такую ​​технику в одном из проектов, чтобы избежать дублирования кода.Также вы можете передать делегату Func<U> в конструктор, если ваш CoolClass преодолел ограничение new ():)

...