Подтвердить что ты не робот

Что такое 'Classname <T>, где T: Classname <T>' делать?

Я читал немецкую статью в Википедии о шаблоне прототипа. В разделе примера содержалась общая реализация С#, используя следующее:

abstract class Prototype<T> where T : Prototype<T> { ... }
...
class ConcretePrototype : Prototype<ConcretePrototype> { ... }

Как это работает? Как вы можете ограничить T одним и тем же общим классом? Как вы можете использовать класс для получения общего типа, используя его?

Я не программирую С#, но это кажется интересным.

Источник: Прототип шаблона

4b9b3361

Ответ 1

ProtoType<T> имеет метод Clone, который возвращает конкретный прототип безопасным образом, поэтому T должен быть определен как параметр типа. Поскольку тип T должен быть только классом, производным от Prototype, тогда строка:

abstract class Prototype<T> where T : Prototype<T> { ... }

требуется ограничить T только подклассом Prototype. Поскольку Prototype является общим, ProtoType<T> должен быть указан в ограничении.

Теоретически декларация для ConcretePrototype должна быть только:

class ConcretePrototype : Prototype<> { ... }

(или аналогичный синтаксис). Но компилятор С# не поддерживает вывод параметров типа таким образом. Если вы положили что-то вроде:

class ConcretePrototype : Prototype<string> { ... }

вы получите ошибку компиляции, поскольку она знает, что она должна быть Prototype<ConcretePrototype>, из-за ограничения Prototype. Компилятор требует явного объявления об этом, поэтому:

class ConcretePrototype : Prototype<ConcretePrototype> { ... }

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

Ответ 2

Ну, в основном это просто ограничивает TypeParameter T типом, наследующим от Prototype с его собственным типом в качестве TypeParameter.

Таким образом, только классы, наследующие от Prototype<T>, могут быть переданы как T.

(возможно) Рабочий пример:

class FirstConcretePrototype : Prototype<FirstConcretePrototype> { } // works

// propably not what the author wanted to happen but...
class SecondConcretePrototype : Prototype<FirstConcretePrototype> { } // works (at least compiles) too, funny huh?

Имейте в виду, что SecondConcretePrototype является допустимым С#, но, вероятно, завершится неудачно, поскольку T - FirstConcretePrototype и в Prototype<T> Clone -метод this -объект (который имеет тип SecondConcretePrototype) получает значение FirstConcretePrototype. Учитывая, что этот прилив невозможен, он всегда будет терпеть неудачу во время выполнения, потому что в SecondConcretePrototype

public T Clone()
{
    return (T)this.MemberwiseClone();
}

переводится на

// still in SecondConcretePrototype ...
public FirstConcretePrototype Clone()
{
    return (FirstConcretePrototype)this.MemberwiseClone(); // 'this' is of type SecondConcretePrototype 
}

Я знаю, что это ничто из здравомыслящего человека никогда не будет печататься, но стоит отметить это и делает этот шаблон несколько "нечистым" ИМО, потому что Typerestriction не защищает вас от того, что вы делаете дерьмовые вещи.

Неудачный пример

class AnyType{ }

class ThirdConcretePrototype : Prototype<AnyType> { } // fails at compiletime, AnyType does not inhertit from Prototype<T>

Ответ 3

Я попытаюсь это объяснить, но сначала посмотрю на короткий, но рабочий пример:

abstract class Prototype<T> where T : Prototype<T> 
{
    public T Clone()
    {
        return this.MemberwiseClone() as T;
    }
}

class ConcretePrototype1 : Prototype<ConcretePrototype1> 
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class ConcretePrototype2 : Prototype<ConcretePrototype2> 
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        ConcretePrototype1 inst1 = new ConcretePrototype1()
        {
            Id = 1,
            Name = "Jon Skeet"
        };

        ConcretePrototype2 inst2 = new ConcretePrototype2()
        {
            Id = 2,
            Name = "Frodo Torbins"
        };

        ConcretePrototype1 copy1 = inst1.Clone();
        ConcretePrototype2 copy2 = inst2.Clone();

        Console.WriteLine(copy1.Name + "  " + copy1.GetType().Name);
        Console.WriteLine(copy2.Name + "  " + copy2.GetType().Name);
    }
} 

//Выход

Jon Skeet   ConcretePrototype1
Frodo Torbins ConcretePrototype2

Объяснение:

Что это работает?

Как вы видите, шаблон прототипа имеет только один метод Clone(), который создает копию текущего объекта.

Как вы можете ограничить T одним и тем же общим классом?

Нет причин, по которым вы не можете ограничить параметр типа тем же классом или производным классом, который наследуется от этого базового абстрактного класса. Это приведет к чему-то вроде: Prototype<Prototype<T>> или Prototype<Derived<T>> (оба действия предполагают, что Derived наследует класс Prototype)

Как вы можете использовать класс для генерации из общего типа, используя его?

Когда мы объявляем класс ConcretePrototype1, мы выводим его из Prototype<ConcretePrototype1> (self), поэтому мы даем компилятору понять, что шаблон Prototype<T> должен использовать ConcretePrototype1 как его параметр T. Это приводит к логике, где метод Clone() возвращает экземпляр ConcretePrototype1, потому что это то, что наш T. Такая же логика используется для класса ConcretePrototype2.

Короче говоря, эта абстрактная подпись класса для шаблона Prototype:

abstract class Prototype<T> where T : Prototype<T> 

ограничивает свой метод Clone() только для создания экземпляров производных классов и не более того.