Constraints in generic methods and delegates. Examples. Applying constraints for multiple types
Contents
- 1. Applying constraints in generic methods. Example
- 2. Applying constraints in generic delegates. Example
- 3. The syntax for declaring a constraint for several types T1, T2, …, TN. Class constraint declaration
- 4. Syntax for declaring a generic interface that takes as parameters the types T1, T2, …, TN
- 5. Syntax for declaring a generic structure that contains bounds for types T1, T2, …, TN
- 6. The syntax for declaring a constraint in a generic method that takes parameters of multiple types containing bounds. Example
- 7. Syntax for declaring constraints on multiple types in a generic delegate
- 8. An example of a class that receives as parameters the types T1, T2 on which bounds are imposed
- Related topics
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
- Generics. Basic concepts. Generic classes and structures
- Bounded types. General concepts. Bounds on reference type and value type
- Constraints on the constructor, base class and interface
⇑