Ограничения на конструктор, базовый класс и интерфейс
Содержание
- 1. Ограничение на конструктор: where T : new(). Особенности использования
- 2. Пример, объясняющий применение ограничения new()
- 3. Ограничение на базовый класс: where T : BaseClass
- 4. Пример, объясняющий ограничение на базовый класс. Рисунок
- 5. Ограничение на интерфейс: where T : Interface
- 6. Пример, объясняющий ограничение на интерфейс
- 7. Использование ограничений для установления связи между двумя параметрами типа. Неприкрытое ограничения типа в ограничениях where T: BaseClass. Пример
- Связанные темы
Поиск на других ресурсах:
1. Ограничение на конструктор: where T : new(). Особенности использования
Ограничение типа new() позволяет получить экземпляр (объект) обобщенного типа T в общем классе. Для того, чтобы получить этот экземпляр, нужно чтобы тип (класс) T обязательно содержал конструктор без параметров (конструктор по умолчанию).
Как известно, конструктор без параметров в классе может быть в одном из двух случаев:
- если в классе не объявлено ни одного конструктора. В этом случае компилятор создает собственный конструктор без параметров, который еще называется конструктор по умолчанию;
- если в классе конструктор без параметров объявляется явно. В этом случае кроме этого конструктора класс может содержать реализации других дополнительных конструкторов с параметрами.
Возможна ситуация, когда в классе реализованы один или несколько конструкторов с параметрами а конструктора без параметров не объявлено. В этом случае такой класс (тип) не подойдет для создания экземпляра (объекта) типа T.
⇑
2. Пример, объясняющий применение ограничения new()
Пусть заданы два класса:
- класс MyClass, содержащий явно заданный конструктор без параметров;
- клас GenClass<T>, устанавливающий ограничение new() для типа T.
Сокращенный программный код классов следующий:
// Класс, который содержит явно заданный конструктор без параметров class MyClass { // явно заданный конструктор без параметров public MyClass() { // ... } } // Обобщенный класс, получающий параметром тип T. // Тип T имеет ограничение, которое требует, чтобы тип T содержал // в своей реализации конструктор без параметров class GenClass<T> where T : new() { // Внутренняя переменная типа T T obj; // Конструктор класса GenClass<T> public GenClass() { // Создать экземпляр типа T obj = new T(); // работает, поскольку указано new() } }
Как видно из вышеприведенного кода, в обобщённом классе создается экземпляр обобщенного типа T. Соответственно экземпляр обобщенного класса создается стандартным способом без проблем
... // Создать экземпляр обобщенного класса GenClass<MyClass> obj = new GenClass<MyClass>(); ...
Если из класса MyClass убрать конструктор без параметров, а вместо него создать другой конструктор с параметрами, то при создании экземпляра класса GenClass<T> компилятор выдаст ошибку
// Класс, который не содержит конструктор без параметров class MyClass { // Некоторый конструктор з параметрами, // конструктора без параметров нету public MyClass(int a) { // ... } } // Обобщенный класс, получающий параметром тип T. class GenClass<T> where T : new() { // ... } ... // Создать экземпляр обобщенного класса GenClass<MyClass> obj = new GenClass<MyClass>(); // здесь ошибка компиляции ...
Если из объявления класса GenClass<T> убрать ограничение where T: new() как показано ниже
// Класс не устанавливает ограничение new() на тип T class GenClass<T> { // Внутренняя переменная типа T T obj; // Конструктор класса GenClass<T> public GenClass() { // Создать экземпляр типа T невозможно! obj = new T(); // здесь не работает, ошибка на этапе компиляции } }
то в конструкторе класса GenClass<T> создать экземпляр типа T будет невозможно.
⇑
3. Ограничение на базовый класс: where T : BaseClass
При объявлении обобщенного класса с использованием ограничения на базовый класс в качестве ограничения указывается имя класса. Этот класс может быть базовым для многих классов, образующих иерархию. Таким образом, дается информация компилятору об ограниченном множестве классов, которые будут использоваться в качестве аргумента типа.
Ограничение на базовый класс дает следующие преимущества:
- в обобщенном классе используется ограниченное количество членов, определенных в базовом классе и классами, производными от него. Все остальные классы и их элементы не используются («отфильтровываются»). Теперь компилятор знает о типах членов, которые может иметь аргумент типа;
- обеспечивается гарантированное использование необходимого множества классов в качестве аргумента типа. Это множество классов ограничивается именем базового класса. Если при создании экземляров обобщенного класса попробовать указать тип, который не входит в нужное множество классов, то возникнет ошибка на этапе компиляции. Такой подход корректен и позволяет избежать трудноуловимых ошибок, которые могут возникнуть на этапе выполнения программы.
⇑
4. Пример, объясняющий ограничение на базовый класс. Рисунок
Пусть задано программный код классов с именами A, B, C, D, E, F, G, H, I а также программный код обобщенного класса MyClass<T> в котором тип T ограничивается базовым классом D
// Перечень классов, образующих иерархию class A { // ... } class B : A { // ... } class C : A { // ... } class D : A { // ... } class E : D { // ... } class F : D { // ... } class G : F { // ... } class H : B { // ... } class I : B { // ... } // Обобщенный класс, получающий параметром тип T, который ограничивается классом D class MyClass<T> where T : D { // ... }
Согласно вышеприведенному коду, классы A, B, C, D, E, F, G, H, I образуют иерархию как показано на рисунке 1.
Рисунок 1. Иерархия классов A, B, C, D, E, F, G, H, I. Ограничение на базовый класс D
Согласно рисунку 1, при объявлении экземпляра класса MyClass<T> допускается использовать типы (классы) D, E, F, G в качестве аргумента типа T
// Объявить экземпляр класса MyClass<T> в котором // в качестве аргумента выступает тип D MyClass<D> obj1 = new MyClass<D>(); // Ok! // Аргументом выступает класс E, который есть подклассом класса D MyClass<E> obj2 = new MyClass<E>(); // Ok! // Аргументом выступает класс F, который есть подклассом класса D MyClass<F> obj3 = new MyClass<F>(); // Ok! // Подкласс класса F также может быть аргументом типа T класса MyClass MyClass<G> obj4 = new MyClass<G>(); // Ok!
При попытке объявить экземпляр класса MyClass<T> с любым другим типом аргумента возникнет ошибка на этапе компиляции
// Попытка использовать другой класс, который не входит в иерархию // где базовым классом выступает класс D MyClass<int> obj = new MyClass<int>(); // ошибка на этапе компиляции // Класс A является базовым для класса D, однако он не входит в иерархию, // в которой базовым классом служит класс D. Поэтому это также ошибка MyClass<A> obj5 = new MyClass<A>(); // ошибка на этапе компиляции // Использование в качестве аргумента типа класса из библиотеки .NET Framework // также является ошибкой MyClass<System.IO.TextWriter> obj6 = new MyClass<System.IO.TextWriter>(); // ошибка компиляции
Таким образом ограничение на базовый класс позволяет отфильтровать классы, использование которых в качестве аргумента типа для заданного обобщенного класса не имеет смысла.
⇑
5. Ограничение на интерфейс: where T : Interface
Ограничения на интерфейс требует реализации одного или нескольких интерфейсов в качестве аргумента типа обобщенного класса.
Как известно, в интерфейсе объявляется перечень методов (элементов), которые обязательно нужно реализовать в классах, наследующих этот интерфейс. Если для некоторого обобщенного класса в качестве ограничения типа T задается имя интерфейса, то этот тип должен содержать реализацию этого интерфейса.
Ограничения на интерфейс применяется в следующих случаях:
- когда нужно использовать члены интерфейса в общем классе;
- когда нужно гарантировать использование только тех аргументов типа, которые реализуют указанный интерфейс.
Допускается после ключевого слова where указывать перечень нескольких интерфейсов. Общая форма такого объявления примерно следующая
class ClassName<T> where T : Interface1, Interface2, ... InterfaceN { ... }
здесь
- Interface1, Interface2, InterfaceN – перечень имен интерфейсов, которые должен реализовать тип T.
В случае с несколькими интерфейсами тип T должен содержать реализацию методов всех интерфейсов. Если не будет полностью реализован хотя бы один интерфейс, компилятор будет выдавать ошибку.
⇑
6. Пример, объясняющий ограничение на интерфейс
Пусть заданы следующие элементы:
- интерфейс IInterface, в котором объявляется один метод Print();
- класс InformClass, который реализует интерфейс IInterface;
- класс MyClass, который не реализует интерфейс IInterface;
- обобщенный (generic) класс GenClass<T> в котором тип T ограничивается именем интерфейса IInterface.
Ниже приводится сокращенный программный код вышеописанных элементов
... // Ограничение на интерфейс // 1. Имеется интерфейс с некоторым методом interface IInformation { // Некоторый метод в интерфейсе, метод выводит сообщение void PrintInfo(string message); } // 2. Класс, реализующий интерфейс IInfomation class InformClass : IInformation { // Реализация метода PrintInfo() public void PrintInfo(string message) { Console.WriteLine("InformClass: " + message); } } // 3. Класс, который НЕ РЕАЛИЗУЕТ интерфейс IInformation class MyClass { // Этот класс также содержит собственный метод PrintInfo() public void PrintInfo(string message) { Console.WriteLine("MyClass: " + message); } } // 4. Обобщенный класс, который содержит ограничение на интерфейс IInformation class GenClass<T> where T : IInformation { // ... } ...
Если попытаться объявить экземпляр (объект) класса GenClass<T>, в котором аргументом типа T выступает класс InformClass
// 1. Попытка объявления экземпляра обобщенного класса, // в котором параметром типа выступает класс InformClass GenClass<InformClass> obj1 = new GenClass<InformClass>(); // работает
то такой код будет работать правильно. Это связано с тем, что класс InformClass реализует интерфейс IInformation
// 2. Класс, который реализует интерфейс IInfomation class InformClass : IInformation { ... }
Если попытаться объявить экземпляр класса GenClass<T>, в котором аргументом типа выступает тип MyClass, то возникнет ошибка на этапе компиляции
// 2. Попытка использовать параметром типа класс MyClass GenClass<MyClass> obj2 = new GenClass<MyClass>(); // ошибка компиляции
Это объясняется тем, что класс MyClass не реализует интерфейс IInformation
// Класс не реализует интерфейс IInformation class MyClass { ... }
В этом случае срабатывает ограничение
where T : IInformation
в объявлении класса GenClass<T>.
⇑
7. Использование ограничений для установления связи между двумя параметрами типа. Неприкрытое ограничения типа в ограничениях where T: BaseClass. Пример
В обобщенных классах при использовании ограничений на базовый класс
where T : BaseClass
можно устанавливать связь между двумя параметрами типа.
В наиболее общем случае объявление такого обобщенного класса следующее
class MyClass<T1, T2> where T1:T2 { // ... }
При таком объявлении указывается, что:
- обобщенный класс MyClass получает параметрами два типа T1, T2;
- тип T1 ограничен типом T2.
Ограничение на параметр типа T1 называется неприкрытым ограничением типа.
Пример. Пусть заданы следующие классы:
- класс A, который есть базовым для классов B, C;
- классы B, C, которые унаследованы от класса A;
- обобщенный класс GenClass<T1, T2>, использующий ограничение типа T1 типом T2.
Программный код сокращенной реализации вышеуказанных классов следующий
// Некоторая иерархия классов class A { // ... } class B : A { // ... } class C : A { // ... } // Использование ограничений для установления связи между двумя параметрами типа. // В классе GenClass тип T1 ограничивается типом T2. class GenClass<T1, T2> where T1 : T2 { // ... }
Теперь можно создавать экземпляр класса MyClass<T1, T2> следующим образом
// Создание экземпляра класса: GenClass<T1, T2> where T1:T2 GenClass<B, A> obj = new GenClass<B, A>(); // Ok!
Вышеприведенный фрагмент является правильным, поскольку класс B является подклассом класса A.
Если попытаться создать экземпляр класса другим способом
// здесь ошибка, класс A не есть подклассом класса B GenClass<A, B> obj = new GenClass<A, B>(); // ошибка компиляции // здесь также ошибка, класс C не есть подклассом класса B GenClass<C, B> obj2 = new GenClass<C, B>(); // ошибка компиляции // и это ошибка, класс B не есть подклассом класса C GenClass<B, C> obj3 = new GenClass<B, C>(); // ошибка
то возникнет ошибка на этапе компиляции.
⇑
Связанные темы
- Обобщения. Основные понятия. Обобщенные классы и структуры
- Ограниченные типы. Общие понятия. Ограничения ссылочного и структурного типов
- Ограничения в обобщенных методах и делегатах. Примеры. Применение ограничений для несколько типов
⇑