Обмеження на конструктор, базовий клас та інтерфейс
Зміст
- 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() 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>(); // помилка
то виникне помилка на етапі компіляції.
⇑
Зв’язані теми
- Узагальнення. Основні поняття. Узагальнені класи та структури
- Обмежені типи. Загальні поняття. Обмеження посилального та значимого типів
- Обмеження в узагальнених методах та делегатах. Приклади. Застосування обмежень для декількох типів
⇑