C#. Узагальнення. Обмежені типи. Загальні поняття

Узагальнення. Обмежені типи. Загальні поняття. Обмеження посилального та значимого типів


Зміст


Пошук на інших ресурсах:




1. Поняття про обмежені типи в узагальненнях. Випадки застосування обмежень. Ключове слово where. Перелік можливих обмежень

При використанні узагальнень елемент програми (клас, структура, інтерфейс, …) отримує параметром деякий узагальнений тип T та використовує його для реалізації розв’язку задачі. Мова C# дозволяє задати обмеження для параметру типу T. Ці обмеження визначають вимоги, яким повинен відповідати тип даних T.

Обмеження типів необхідне у наступних випадках:

  • якщо програміст створює власні класи або власну ієрархію класів, які виступають параметрами типу T і тільки ці класи повинні виступати аргументами типу. У цьому випадку задається ім’я класу або ім’я базового класу в ієрархії. Іншими словами, коли потрібно гарантувати, що в якості аргументів типу T буде обрано строго визначену множину класів з заданої ієрархії;
  • коли потрібно повідомити компілятор про те, що потрібно використовувати методи (властивості) строго визначених типів (класів, структур тощо).

Загальна форма оголошення обмеження для типу T наступна:

where T : bounds

тут

  • bounds – обмеження, що накладаються на використання типу T.

Нижче наведено перелік можливих обмежень, які може отримувати bounds:

  • where T:struct – обмеження типу значення. Параметр типу повинен бути успадкований від System.ValueType, тобто бути структурним типом;
  • where T:class – обмеження типу посилання. Параметр типу повинен бути посилального типу, тобто не повинен бути успадкований від System.ValueType;
  • where T: new() – параметр типу повинен мати конструктор без параметрів (конструктор за замовчуванням);
  • where T:BaseClass – параметр типу повинен бути класом BaseClass або класом, що є похідним від нього;
  • where T:Interface – параметр типу повинен реалізовувати інтерфейс з іменем Interface.

Допускається задавання декількох видів обмежень одночасно. У цьому випадку перелік обмежень задається через кому, наприклад

where T : class, IEquatable, new()

Тут задаються одночасно 3 обмеження:

  • аргумент типу T повинен бути посилального типу (class);
  • аргумент типу T повинен реалізовувати інтерфейс IEquatable;
  • аргумент типу T повинен містити конструктор за замовчуванням – new().

 

2. Обмеження посилального типу: where T : class

Обмеження посилального типу застосовуються, коли потрібно заборонити використання значущих типів (int, float, double тощо) в якості аргументу типу. Більш детально про особливості посилальних типів описується тут.

У цьому випадку обмеження аргументу типу T може бути:

  • будь-яким класом зі стандартної бібліотеки .NET Framework або класом сторонніх розробників;
  • будь-яким власно-розробленим класом програми.

 

3. Приклад, що пояснює використання обмеження посилального типу class

Нехай задано клас RefTypes<T>, в якому тип T обмежується ключовим словом class

// Клас, в якому тип T може бути
// тільки посилальним типом
class RefTypes<T> where T : class
{
  // ...
}

Це означає, що аргументом типу T може бути будь-який посилальний тип з бібліотеки .NET, наприклад, String чи System.IO.BinaryReader

// працює, оскільки тип String - це є посилальний тип
RefTypes<String> ref1 = new RefTypes<String>();

// працює, оскільки тип System.IO.BinaryReader - це є посилальний тип
RefTypes<System.IO.BinaryReader> ref3 = new RefTypes<System.IO.BinaryReader>();

Також аргументом типу T може бути будь-який тип, що реалізований у програмі. Якщо у програмі оголошується клас, інтерфейс та структура

// Користувацький клас у програмі
class MyClass
{
  // ...
}

// Користувацький інтерфейс
interface MyInterface
{
  void Print();
}

// Користувацька структура в програмі
struct MyStruct
{
  // ...
}

то створити екземпляр класу з аргументами типів MyClass та MyInterface допускається, оскільки вони відносяться до аргументів посилального типу

// працює, оскільки MyClass - власно-розроблений у програмі клас
RefTypes<MyClass> obj = new RefTypes<MyClass>();

// працює, оскільки MyInterface - посилального типу
RefTypes<MyInterface> mi = new RefTypes<MyInterface>();

А от при спробі створення екземпляру типу MyStruct чи int (або іншого типу-значення) компілятор видасть помилку

// Спроба створити екземпляр класу RefTypes з аргументом типу int
RefTypes<int> rt1 = new RefTypes<int>(); // помилка компіляції
RefTypes<Int32> rt2 = new RefTypes<int>(); // теж помилка компіляції

// помилка компіляції, оскільки MyStruct - це є структура,
// яка відноситься до типів значень
RefTypes<MyStruct> ms = new RefTypes<MyStruct>();

 

4. Обмеження типу where T : struct. Особливості використання

Обмеження значимих типів struct використовуються для заборони використання посилальних типів в якості аргументів типів. Більш детально про типи-значення та типи-посилання описується тут.

Це обмеження є протилежним до обмеження типу class (дивіться попередні пункти). В обмеженні типу struct дозволяється використовувати наступні типи:

  • структурні типи такі як int, float, char, double та інші а також їхні синоніми Int32, Boolean, Char, Single, Double та інші;
  • користувацькі структурні типи (структури), які оголошуються з використанням ключового слова struct.

 

5. Приклад, що пояснює використання обмеження типу struct

Нехай задано наступне оголошення класу ValTypes<T> в якому узагальнений тип T обмежений ключовим словом struct

// Клас, в якому тип T може бути тільки типом-значення
class ValTypes<T> where T : struct
{
  // ...
}

При такому оголошенні допускається оголошувати екземпляри класу ValTypes<T>, в якому аргументами типу виступає будь-який тип значення, наприклад int чи double

// Оголошення екземплярів для типу int (Int32)
ValTypes<int> obj1 = new ValTypes<int>();
ValTypes<Int32> obj2 = new ValTypes<int>();

// Оголошення екземплярів для типу double (Double)
ValTypes<double> obj3 = new ValTypes<double>();
ValTypes<Double> obj4 = new ValTypes<Double>();

Це пояснюється тим, що типи int, double, float та інші а також їх синоніми (Int32, Double, Single, …) відносяться до значимих типів – вони є структурами.

Також можна оголошувати екземпляри користувацьких структур чи структур з бібліотеки .NET Framework.

Якщо у програмі оголошується структура MyStruct

// Користувацька структура в програмі
struct MyStruct
{
  // ...
}

то тип цієї структури може бути застосований як аргумент типу T при оголошенні екземпляру ValTypes<T>

// Оголошення екземпляру ValueTypes<T> з аргументом типу,
// яким виступає структура MyStruct
ValTypes<MyStruct> ms = new ValTypes<MyStruct>();

Однак, якщо спробувати оголосити екземпляр класу ValTypes<T> з будь-яким посилальним типом, то виникне помилка на етапі компіляції

// Користувацький клас у програмі
class MyClass
{
  // ...
}

...

// Помилка на етапі компіляції
ValTypes<MyClass> obj = new ValTypes<MyClass>();

Те саме стосується і класів з бібліотеки .NET Framework

// Клас String є посилального типу
ValTypes<String> obj = new ValTypes<String>(); // помилка компіляції

// Клас StreamReader також посилального типу
ValTypes<System.IO.TextReader> obj = new ValTypes<System.IO.TextReader>(); // помилка компіляції

Таку реакцію компілятора забезпечує обмеження struct при оголошенні класу ValTypes<T>.

 


Зв’язані теми