// Дополнительные методы для MiniVan.
}
В соответствии с законами наследования, в коллекцию CarCollection‹T›, созданную с параметром типа Car, можно добавлять и типы MiniVan и SportsCar.
// CarCollection‹Car› может хранить любой тип, производный от Car.
CarCollection‹Car› myCars = new CarCollection‹Car›;
myInts.AddCar(new MiniVan("Family Truckster", 55);
myInts.AddCar(new SportsCar("Crusher", 40));
Это синтаксически корректно, но что делать, если вдруг понадобится добавить в CarCollection‹T› новый открытый метод, например, с именем PrintPetName? Такая задача кажется простой – достаточно получить доступ к подходящему элементу из List‹T› и вызвать свойство PetName.
// Ошибка!
// System.Объект не имеет свойства о именем PetName.
public void PrintPetName(int pos) {
Console.WriteLine(arCars[pos].PetName);
}
Однако в таком виде программный код скомпилирован не будет, поскольку истинная суть ‹Т› еще не известна, и вы не можете с уверенностью утверждать, что какой-то элемент типа List‹T› будет иметь свойство PetName. Когда параметр типа не имеет никаких ограничений (как в данном случае), обобщенный тип называется
Вы можете попытаться "обмануть" компилятор путем преобразования элемента, возвращенного из Метода индексатора List‹T›, в строго типизованный объект Car, чтобы затем вызвать petName возвращенного объекта.
// Ошибка!
// Нельзя превратить тип 'Т' в 'Car'!
public void PrintPetName(int pos) {
Console.WriteLine(((Car)arCars[pos]).PetName);
}
Но это тоже не компилируется, поскольку компилятор не знает значения параметра типа ‹Т› и не может гарантировать, что преобразование будет законным.
Для решения именно таких проблем обобщения .NET могут опционально определяться с ограничениями, для чего используется ключевое слово where. В .NET 2.0 обобщения могут иметь ограничения, описанные в табл. 10.2.
Таблица 10.2. Возможные ограничения обобщений для параметров типа
| Ограничение обобщения | Описание |
|---|---|
| where T: struct | Параметр типа ‹T› должен иметь в цепочке наследования System.ValueType |
| where T: class | Параметр типа ‹T› |
| where T: new | Параметр типа ‹T› должен иметь конструктор, заданный по умолчанию. Это полезно тогда, когда обобщенный тип должен создать экземпляр параметра типа, а вы не имеете ясных предположений о формате пользовательских конструкторов. Заметьте, что это ограничение должно быть последним в списке ограничений, если у типа их несколько |
| where T: | Параметр типа ‹T› должен быть производным класса, указанного параметром |
| where T: | Параметр типа ‹T› должен реализовывать интерфейс, указанный параметром |
При наложении ограничений с помощью ключевого слова where список ограничений размещается после имени базового класса обобщенного типа и списка интерфейсов. В качестве конкретных примеров рассмотрите следующие ограничения обобщенного класса MyGenericClass.
// Вложенные элементы должны иметь конструктор,
// заданный по умолчанию.
public class MyGenericClass‹T› where T: new {…}
// Вложенные элементы должны быть классами, реализующими IDrawable
// и поддерживающими конструктор, заданный по умолчанию.
public class MyGenericClass‹T› where T: class, IDrawable, new {…}