C#. Обмеження в узагальнених методах та делегатах

Обмеження в узагальнених методах та делегатах. Приклади. Застосування обмежень для декількох типів


Зміст


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

 

 

1. Застосування обмежень в узагальнених методах. Приклад

Обмеження можуть застосовуватись до окремих методів класів. У цьому випадку синтаксис оголошення обмеження в методі класу наступний

return_type MethodName<T>(parameters) where T : bounds
{

}

тут

  • return_type – тип, що повертає метод. Цей тип також може бути типом T;
  • T – тип, що отримує метод в якості параметру;
  • MethodName(parameters) – ім’я методу з переліком параметрів;
  • bounds – обмеження, що накладаються на тип T. Обмеження може бути одним з переліку struct, class, BaseClass, Interface, new().

Приклад. У прикладі оголошується два класи:

  • клас MyClass, в якому оголошується узагальнений метод Print<T>();
  • клас A. Посилання на цей клас буде передаватись в метод Print<T>().

Фрагмент програмного коду класів та методу наступний

class MyClass
{
  // Обробляються тільки типи-посилання
  public void Print<T>(T obj) where T : class
  {
    Console.WriteLine("obj: " + obj.ToString());
  }
}

// Деякий клас
class A
{
  // ...
}

...

Як видно з вищенаведеного коду, у класі MyClass метод Print<T>() отримує параметром тип T, на який накладається обмеження

where T : class

Це означає, що в якості типу T можуть бути використані тільки типи-посилання.

У функції main() (або інших методах) метод Print<T>() може бути використаний наступним чином

...

// 1. Оголосити екземпляр класу MyClass
MyClass obj1 = new MyClass();

// 2. Оголосити екземпляр класу A
A obj2 = new A(); // obj2 - посилального типу

// 3. Викликати метод Print<T> та передати йому посилальний тип (obj2)
obj1.Print<A>(obj2); // працює!

...

Якщо спробувати використати тип-значення, то виникне помилка на етапі компіляції

// Викликати метод Print<T> та передати йому тип-значення (val)
int val = 38; // val - тип-значення (значимий тип)
obj1.Print<int>(val); // тут помилка компіляції

 

2. Застосування обмежень в узагальнених делегатах. Приклад

Обмеження можуть застосовуватись до делегатів. Загальна форма застосування обмеження до делегату, що отримує параметром тип T, виглядає наступним чином

delegate return_type DelegateName<T>(parameters) where T : bounds;

тут

  • DelegateName – ім’я делегату, який повертає тип return_type та отримує параметром список parameters;
  • bounds – перелік обмежень, які накладаються на тип T.

Приклад. Оголошується делегат Oper3, що отримує тип T в якості параметру типу. Тип T обмежується структурними типами (struct).
Також оголошується структура Operations, яка містить три статичні методи:

  • Max3() – визначає максимальне значення між трьома числами типу double;
  • Min3() – визначає мінімальне значення між трьома числами типу double;
  • Avg3() – визначає середнє значення між трьома числами типу double.

 

// Узагальнений делегат, в якому тип T
// повинен бути структурного типу
delegate T Oper3<T>(T a, T b, T c) where T : struct;

// Структура, що містить статичні методи оперування даними
struct Operation
{
  // Максимальне між трьома числами
  public static double Max3(double a, double b, double c)
  {
    double max = a;
    if (max < b) max = b;
    if (max < c) max = c;
    return max;
  }

  // Середнє між трьома числами
  public static double Avg3(double a, double b, double c)
  {
    if (((a > b) && (a < c)) ||
        ((a > c) && (a < b)))
      return a;
    if (((b > a) && (b < c)) ||
        ((b > c) && (b < a)))
      return b;
    return c;
  }

  // Мінімальне між трьома числами
  public static double Min3(double a, double b, double c)
  {
    double min = a;
    if (min > b) min = b;
    if (min > c) min = c;
    return min;
  }
}

В іншій функції (наприклад, функції main()) делегат Oper3 може бути використаний наступним чином

...

// Оголосити посилання на узагальнений делегат
Oper3<double> op;

// Присвоїти посиланню адресу методу Operation.Max()
op = Operation.Max3;

// Викликати метод Operation.Max() через делегат
double max = op(2.8, 3.5, 1.4);
Console.WriteLine("max = {0}", max); // max = 3.5

// Викликати метод Avg() через делегат op
op = Operation.Avg3;
double avg = op(2.8, 3.5, 1.4);
Console.WriteLine("avg = {0}", avg); // avg = 2.8

...

 

3. Синтаксис оголошення обмеження для декількох типів T1, T2, …, TN. Оголошення обмеження у класі

Узагальнений елемент програми (клас, структура, інтерфейс, метод, делегат) може отримувати в якості параметрів декілька типів T1, T2, …, TN.

Синтаксис оголошення класу, в якому декілька параметрів типу отримують наступні обмеження:

class ClassName<T1, T2, ..., TN>
    where T1 : bounds1
    where T2 : bounds2
    ...
    where TN : boundsN
{
    // ...
}

тут

  • T1, T2, TN – параметри типів, якими оперує клас;
  • bounds1, bounds2, boundsN – відповідно обмеження на типи T1, T2, TN.

 

4. Синтаксис оголошення узагальненого інтерфейсу, який отримує параметрами типи T1, T2, …, TN

Синтаксис оголошення інтерфейсу, який використовує декілька типів, наступний

interface InterfaceName<T1, T2, ..., TN>
    where T1 : bounds1
    where T2 : bounds2
    ...
    where TN : boundsN
{
    // ...
}

Клас, що реалізує даний інтерфейс повинен враховувати обмеження для кожного типу. Інакше виникне помилка компіляції.

 

5. Синтаксис оголошення узагальненої структури, яка містить обмеження для типів T1, T2, …, TN

Синтаксис оголошення структури, яка містить обмеження для декількох типів, виглядає наступним чином

struct StructName<T1, T2, ..., TN>
    where T1 : bounds1
    where T2 : bounds2
    ...
    where TN : boundsN
{
    // ...
}

 

6. Синтаксис оголошення обмеження в узагальненому методі, який отримує параметрами декілька типів які містять обмеження. Приклад

Якщо в деякому елементі програми (клас, структура, інтерфейс) оголошується узагальнений метод, який отримує параметрами декілька типів T1, T2, …, TN, то для кожного типу можуть накладатись обмеження. У цьому випадку синтаксис оголошення методу наступний:

return_type MethodName<T1, T2, ..., TN>(parameters)
    where T1 : bounds1
    where T2 : bounds2
    ...
    where TN : boundsN
{
    // ...
}

тут

  • return_type – тип, який повертає метод;
  • T1, T2, TN – типи-параметри;
  • bounds1, bounds2, boundsN – відповідно обмеження на типи T1, T2, TN;
  • parameters – параметри, які отримує метод.

Приклад. Оголошується клас з іменем SomeClass, який містить узагальнений метод Method<T1, T2, T3> який отримує три параметри типу. Для першого параметру T1 встановлюється обмеження struct. Для другого параметру T2 встановлюється обмеження class. Для параметру T3 встановлюється обмеження базового класу FileStream та обмеження на наявність конструктору без параметрів.

// Клас, в якому оголошено метод, що оперує трьома типами
class SomeClass
{
  public void Method<T1, T2, T3>()
    where T1 : struct
    where T2 : class
    where T3 : System.IO.FileStream, new()
  {
    // ...
  }
}

 

7. Синтаксис оголошення обмежень на декілька типів в узагальненому делегаті

Якщо потрібно щоб делегат оперував декількома типами T1, T2, …, TN, на які накладаються обмеження, то синтаксис оголошення такого делегату наступний

delegate return_type DelegateName<T1, T2, ..., TN>(parameters)
  where T1 : bounds1
  where T2 : bounds2
  ...
  where TN : boundsN;

тут

  • return_type – тип, який повертає делегат;
  • T1, T2, TN – типи-параметри;
  • restrictions1, restrictions2, restrictionsN – відповідно обмеження на типи T1, T2, TN;
  • parameters – параметри, які отримує делегат.

 

8. Приклад класу, що отримує параметрами типи T1, T2 на які накладаються обмеження

У прикладі приведено фрагмент програми, в якому оголошується клас, що містить узагальнений метод. Цей метод отримує параметрами два типи T1, T2. На кожен з типів накладається обмеження типу struct. Це обмеження зобов’язує вказувати параметрами типу тільки типи-значення (int, double, Int32, Single, bool, Boolean, …).

using System;

namespace ConsoleApp19
{
  // Застосування обмежень в узагальнених методах

  // Клас, що містить узагальнений метод Print2<T1, T2>,
  // який працює тільки з типами-значеннями

  class MyClass
  {
    // Метод Print2() отримує два параметри типу T1, T2.
    // Метод виводить інформацію про ці типи
    public void Print2<T1, T2>(T1 obj1, T2 obj2)
      where T1 : struct
      where T2 : struct
    {
      // Отримати інформацію про екземпляри obj1, obj2
      Type tp1 = obj1.GetType();
      Type tp2 = obj2.GetType();

      Console.WriteLine("obj1.Name = " + tp1.Name);
      Console.WriteLine("obj1.IsClass = {0}", tp1.IsClass);

      Console.WriteLine("obj2.Name = " + tp2.Name);
      Console.WriteLine("obj2.IsClass = {0}", tp2.IsClass);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Використати метод Print2<T1, T2>() класу MyClass

      // 1. Створити екземпляр класу MyClass()
      MyClass obj1 = new MyClass();

      // 2. Викликати метод Print2<T1, T2>) екземпляру obj1
      obj1.Print2<int, double>(28, 3.88);

      Console.WriteLine("Ok!");
      Console.ReadKey();
    }
  }
}

Результат виконання програми

obj1.Name = Int32
obj1.IsClass = False
obj2.Name = Double
obj2.IsClass = False
Ok!

Якщо в рядку виклику методу Print2<T1, T2> замість типу-значення вказати посилальний тип, наприклад,

// 2. Вказати посилальний тип
String s1 = "Hello world!";
System.Text.StringBuilder s2 = new System.Text.StringBuilder();

// Цей код містить помилку
obj1.Print2<String, System.Text.StringBuilder>(s1, s2); // помилка компіляції

то компілятор видасть помилку. У цьому випадку спрацюють обмеження

where T1 : struct
where T2 : struct

в оголошенні методу Print2().

 


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