Что такое 'Classname <T>, где T: Classname <T>' делать?
Я читал немецкую статью в Википедии о шаблоне прототипа. В разделе примера содержалась общая реализация С#, используя следующее:
abstract class Prototype<T> where T : Prototype<T> { ... }
...
class ConcretePrototype : Prototype<ConcretePrototype> { ... }
Как это работает? Как вы можете ограничить T одним и тем же общим классом? Как вы можете использовать класс для получения общего типа, используя его?
Я не программирую С#, но это кажется интересным.
Источник: Прототип шаблона
Ответы
Ответ 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()
только для создания экземпляров производных классов и не более того.