Наследование обобщенных типов
Один обобщенный класс может быть унаследован от другого обобщенного. При этом можно использовать различные варианты наследования.
Допустим, у нас есть следующий базовый класс Person:
class Person<T>
{
public T Id { get;}
public Person(T id)
{
Id = id;
}
}
Первый вариант заключается в создание класса-наследника, который типизирован тем же типом, что и базовый:
class UniversalPerson<T> : Person<T>
{
public UniversalPerson(T id) : base(id) { }
}
Применение класса:
Person<string> person1 = new Person<string>("34");
Person<int> person3 = new UniversalPerson<int>(45);
UniversalPerson<int> person2 = new UniversalPerson<int>(33);
Console.WriteLine(person1.Id);
Console.WriteLine(person2.Id);
Console.WriteLine(person3.Id);
Второй вариант представляет создание обычного необобщенного класса-наследника. В этом случае при наследовании у базового класса надо явным образом определить используемый тип:
class StringPerson : Person<string>
{
public StringPerson(string id) : base(id) { }
}
Теперь в производном классе в качестве типа будет использоваться тип string. Применение класса:
StringPerson person4 = new StringPerson("438767");
Person<string> person5 = new StringPerson("43875");
// так нельзя написать
//Person<int> person6 = new StringPerson("45545");
Console.WriteLine(person4.Id);
Console.WriteLine(person5.Id);
Третий вариант представляет типизацию производного класса параметром совсем другого типа, отличного от универсального параметра в базовом классе. В этом случае для базового класса также надо указать используемый тип:
class IntPerson<T> : Person<int>
{
public T Code { get; set; }
public IntPerson(int id, T code) : base(id)
{
Code = code;
}
}
Здесь тип IntPerson типизирован еще одним типом, который может не совпадать с типом, который используется базовым классом. Применение класса:
IntPerson<string> person7 = new IntPerson<string>(5, "r4556");
Person<int> person8 = new IntPerson<long>(7, 4587);
Console.WriteLine(person7.Id);
Console.WriteLine(person8.Id);
И также в классах-наследниках можно сочетать использование универсального параметра из базового класса с применением своих параметров:
class MixedPerson<T, K> : Person<T>
where K : struct
{
public K Code { get; set; }
public MixedPerson(T id, K code) : base(id)
{
Code = code;
}
}
Здесь в дополнение к унаследованному от базового класса параметру T добавляется новый параметр K. Также если необходимо при этом задать ограничения, мы их можем указать после названия базового класса. Применение класса:
MixedPerson<string, int> person9 = new MixedPerson<string, int>("456", 356);
Person<string> person10 = new MixedPerson<string, int>("9867", 35678);
Console.WriteLine(person9.Id);
Console.WriteLine(person10.Id);
При этом стоит учитывать, что если на уровне базового класса для универсального параметра установлено ограничение, то подобное ограничение должно быть определено и в производных классах, которые также используют этот параметр:
class Person<T> where T : class
{
public T Id { get;}
public Person(T id) => Id = id;
}
class UniversalPerson<T> : Person<T> where T: class
{
public UniversalPerson(T id) : base(id) { }
}
То есть если в базовом классе в качестве ограничение указано class, то есть любой класс, то в производном классе также надо указать в качестве ограничения class, либо же какой-то конкретный класс.