C#. Constraints in generic methods and delegates

Constraints in generic methods and delegates. Examples. Applying constraints for multiple types


Contents


Search other resources:

1. Applying constraints in generic methods. Example

Constraints can be applied to individual class methods. In this case, the syntax for declaring a constraint in a class method is as follows

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

here

  • return_type – the type returned by the method. This type can also be type T;
  • T – the type that the method receives as a parameter;
  • MethodName(parameters) – method name with a list of parameters;
  • bounds – bounds imposed on type T. The bound can be one of the list of struct, class, BaseClass, Interface, new().

Example.

The example declares two classes:

  • class MyClass, in which the generic method Print<T>() is declared;
  • class A. A reference to this class will be passed to the Print<T>() method.

The snippet of the classes and method code is as follows

class MyClass
{
  // Only reference types are processed
  public void Print<T>(T obj) where T : class
  {
    Console.WriteLine("obj: " + obj.ToString());
  }
}

// A class
class A
{
  // ...
}

As you can see from the above code, in the MyClass class, the Print<T>() method receives as a parameter the type T, to which the constraint is imposed

where T : class

This means that only reference types can be used as type T.

In the main() function (or other methods), the Print<T>() method can be used as follows

...

// 1. Declare an instance of class MyClass
MyClass obj1 = new MyClass();

// 2. Declare an instance of class A
A obj2 = new A(); // obj2 - reference type

// 3. Call the Print<T> method and pass it a reference type (obj2)
obj1.Print<A>(obj2); // Ok!

...

If you try to use a value type, an error will occur at the compile time.

// Call the Print <T> method and pass it a value type (val)
int val = 38; // val - value type
obj1.Print<int>(val); // here's a compilation error

 

2. Applying constraints in generic delegates. Example

Constraints can be applied to delegates. The general form of applying a constraint to a delegate that receives a type T as a parameter is as follows

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

here

  • DelegateName – the name of a delegate that returns return_type and receives a list of parameters as a parameter;
  • bounds – a list of restrictions that are imposed on type T.

Example.

An Oper3 delegate is declared that takes the type T as a type parameter. The type T is limited to structural types (struct).
An Operations structure is also declared, which contains three static methods:

  • Max3() – determines the maximum value between three numbers of the double type;
  • Min3() – determines the minimum value between three double numbers;
  • Avg3() – determines the average value between three double numbers.

 

// A generic delegate in which the type T must be of a structured type
delegate T Oper3<T>(T a, T b, T c) where T : struct;

// A static structure containing methods of data manipulation
struct Operation
{
  // Maximum between three numbers
  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;
  }

  // Average between three numbers
  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;
  }

  // Minimum between three numbers
  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 может быть использован следующим образом

...

// Declare a reference to a generic delegate
Oper3<double> op;

// Assign the address of the Operation.Max () method to the reference
op = Operation.Max3;

// Call the Operation.Max() method via delegate
double max = op(2.8, 3.5, 1.4);
Console.WriteLine("max = {0}", max); // max = 3.5

// Call Avg() method via op delegate
op = Operation.Avg3;
double avg = op(2.8, 3.5, 1.4);
Console.WriteLine("avg = {0}", avg); // avg = 2.8

...

 

3. The syntax for declaring a constraint for several types T1, T2, …, TN. Class constraint declaration

A generalized program element (class, structure, interface, method, delegate) can receive several types T1, T2, …, TN as parameters.

Class declaration syntax in which multiple type parameters receive the following constraints:

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

here

  • T1, T2, TN – type parameters which the class uses;
  • bounds1, bounds2, boundsN – respectively bounds on the types T1, T2, TN.

 

4. Syntax for declaring a generic interface that takes as parameters the types T1, T2, …, TN

The syntax for declaring an interface that uses multiple types is as follows

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

A class that implements this interface must take into account the constraint for each type. Otherwise, a compilation error will occur.

 

5. Syntax for declaring a generic structure that contains bounds for types T1, T2, …, TN

The syntax for declaring a structure containing bounds for several types is as follows

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

 

6. The syntax for declaring a constraint in a generic method that takes parameters of multiple types containing bounds. Example

If in some element of the program (class, structure, interface) a generic method is declared that receives as parameters several types T1, T2, …, TN, then bounds may be imposed on each type. In this case, the syntax for declaring a method is as follows:

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

here

  • return_type – the type returned by the method;
  • T1, T2, TN – parameter types;
  • bounds1, bounds2, boundsN – accordingly bounds on types T1, T2, TN;
  • parameters – the parameters that the method receives.

Example.

A class named SomeClass is declared, which contains a generic method Method<T1, T2, T3> that receives three type parameters. The first parameter, T1, is set to a struct constraint. For the second parameter, T2, the class constraint is set. The T3 parameter is constrained by the base FileStream class and constrained by a parameterless constructor.

// A class in which a method operating on three types is declared
class SomeClass
{
  public void Method<T1, T2, T3>()
    where T1 : struct
    where T2 : class
    where T3 : System.IO.FileStream, new()
  {
      // ...
  }
}

 

7. Syntax for declaring constraints on multiple types in a generic delegate

If you need a delegate to operate on several types T1, T2, …, TN, on which constraints are imposed, then the syntax for declaring such a delegate is as follows

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

here

  • return_type – the type returned by the delegate;
  • T1, T2, TN – types-parameters;
  • bounds1, bounds2, boundsN – accordingly bounds on types T1, T2, TN;
  • parameters – the parameters that the delegate receives.

 

8. An example of a class that receives as parameters the types T1, T2 on which bounds are imposed

The example shows a program fragment that declares a class that contains a generic method. This method receives two types of parameters T1, T2. Each type is subject to a struct type constraint. This restriction obliges to specify type parameters only value types (int, double, Int32, Single, bool, Boolean, …).

using System;

namespace ConsoleApp19
{
  // Applying constraints in generic methods

  // A class containing a generic method Print2<T1, T2> that only works with value types
  class MyClass
  {
    // The Print2() method receives two parameters of type T1, T2.
    // The method displays information about these types
    public void Print2<T1, T2>(T1 obj1, T2 obj2)
      where T1 : struct
      where T2 : struct
    {
      // Get information about instances of 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();
    }
  }
}

The result of the program

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

If you specify a reference type instead of a value type in the call line of the Print2<T1, T2> method, for example,

// 2. Specify a reference type
String s1 = "Hello world!";
System.Text.StringBuilder s2 = new System.Text.StringBuilder();

// This code contains an error
obj1.Print2<String, System.Text.StringBuilder>(s1, s2); // compilation error

then the compiler will throw an error. In this case, the following bounds

where T1 : struct
where T2 : struct

in the method Print2() declaration will be triggered.

 


Related topics