// Вложенные элементы должны иметь конструктор,
// заданный по умолчанию.
public class MyGenericClass‹T› where T: new() {…}
// Вложенные элементы должны быть классами, реализующими IDrawable
// и поддерживающими конструктор, заданный по умолчанию.
public class MyGenericClass‹T› where T: class, IDrawable, new() {…}
// MyGenericClass получается из МуВаsе и реализует ISomeInterface,
// а вложенные элементы должны быть структурами.
public class MyGenericClass‹T›: MyBase, ISomeInterface where T: struct {…}
При построении обобщенного типа, в котором указано несколько параметров типа, вы можете указать уникальный набор ограничений для каждого из таких параметров.
// ‹К› должен иметь конструктор, заданный по умолчанию,
// а ‹Т› должен реализовывать открытый интерфейс IComparable.
public class MyGenericClass‹K, T› where K: new() where T: IComparable‹T› {…}
Если вы хотите изменить тип CarCollection‹T› так, чтобы в него можно было поместить только производные от Car, вы можете записать следующее.
public' class CarCollection‹T›: IEnumerable‹T› where T: Car {
public void PrintPetName(int роs) {
// Поскольку теперь все элементы должны быть из семейства Car,
// свойство PetName можно вызывать непосредственно.
Console.WriteLine(arCars[pos].PetName);
}
}
При таких ограничениях на CarCollection‹T› реализация PrintPetName() становится очень простой, поскольку теперь компилятор может предполагать, что ‹Т› является производным от Car. Более того, если указанный пользователем параметр типа не совместим с Car, будет сгенерирована ошибка компиляции.
// Ошибка компиляции!
CarCollection‹int› myInts = new CarCollection‹int›();
Вы должны понимать, что обобщенные методы тоже могут использовать ключевое слово where. Например, если нужно гарантировать, чтобы методу Swap(), созданному в этой главе выше, передавались только типы, производные от System. ValueType, измените свой программный код так.
// Этот метод переставит любые типы, характеризуемые значениями.
static void Swap‹T›(ref Т а, ref T b) where T: struct {
…
}
Следует также понимать то, что при таком ограничении метод Swap() уже не сможет переставлять строковые типы (поскольку они являются ссылочными).
Отсутствие поддержки ограничений при использовании операций
При создании обобщенных методов для вас может оказаться сюрпризом появление ошибок компилятора, когда с параметрами типа используются операции C# (+, -, *, == и т.д.). Например, я уверен, вы сочли бы полезными классы Add(), Subtract(), Multiply() и Divide(), способные работать с обобщенными типами.
// Ошибка компиляции!
// Нельзя применять операции к параметрам типа!
public class BasicMath‹T› {
public T Add(T arg1, T arg2) { return arg1 + arg2; }
public T Subtract(T arg1, T arg2) { return arg1 – arg2; }
public T Multiply(T arg1, T arg2) { return arg1 * arg2; }
public T Divide(T arg1, T arg2) { return arg1 / arg2; }
}
Как ни печально, этот класс BasicMath‹T› не компилируется. Это может показаться большим ограничением, но не следует забывать, что обобщения являются обобщениями. Конечно, тип System.Int32 может прекрасно работать с бинарными операциями C#. Однако, если, например, ‹T› будет пользовательским классом иди типом структуры, компилятор не сможет сделать никаких предположений о характере перегруженных операций +, -, * и /. В идеале C# должен был бы позволять обобщенному типу ограничения с использованием операций, например, так.
// Только для иллюстрации!
// Этот программный код не является допустимым в C# 2.0.
public class BasicMath‹T› where T: operator +, operator -, operator *, operator / {
public T Add(T arg1, T arg2) { return arg1 + arg2; }
public T Subtract(T arg1, T arg2) { return arg1 – arg2; }
public T Multiply(T arg1, T arg2) { return arg1 * arg2; }
public T Divide(T arg1, T arg2) { return arg1 / arg2; }
}
Увы, ограничения обобщенных типов при использовании операций в C# 2005 не поддерживаются.
Исходный код.Проект CustomGenericCollection размещен в подкаталоге, соответствующем главе 10.
Создание обобщенных базовых классов
Перед рассмотрением обобщенных интерфейсов следует указать на то, что обобщенные классы могут быть базовыми для других классов и могут таким образом определять любое число виртуальных и абстрактных методов. Однако производные типы должны подчиняться определенным правилам, вытекающим из природы обобщенной абстракции. Во-первых, если обобщенный класс расширяется необобщенным, то производный класс должен конкретизировать параметр типа,
Читать дальше