Generics. Bounded types. General concepts. Bounds on reference type and value type
Contents
- 1. The concept of bounded types in generics. Cases of application of bounded types. List of possible bounds
- 2. The constraint of reference type: where T: class
- 3. An example explaining the use of the class reference type constraint
- 4. Constraint of type where T : struct. Features of use
- 5. Example explaining the use of struct constraint
- Related topics
Search other resources:
1. The concept of bounded types in generics. Cases of application of bounded types. List of possible bounds
When using generics, a program element (class, structure, interface, …) receives as a parameter some generic type T and uses it to implement the solution to the problem. C# language allows you to set limits for the type parameter T. These limits define the requirements to be met by the data type T.
Type bounds are required in the following cases:
- if the programmer creates his own classes or his own hierarchy of classes that act as parameters of type T and only these classes should act as type arguments. In other words, when it is necessary to ensure that a strictly defined set of classes in the specified hierarchy will be chosen as arguments of the type T;
- when you need to tell the compiler to use methods (properties) of strictly defined types (classes, structures, etc.).
The general form of declaring a bound for type T is as follows:
where T : bounds
here
- bounds – limits on the use of type T.
Below is a list of possible restrictions that can receive bounds:
- where T: struct – value-type constraint. The type parameter must inherit from System.ValueType, that is, it must be a structured type;
- where T: class is the constraint of the reference type. The type parameter must be of a reference type, that is, it must not inherit from System.ValueType;
- where T: new() – the type parameter must have a parameterless constructor (default constructor);
- where T: BaseClass – the type parameter must be the BaseClass class or a class derived from it;
- where T: Interface – the type parameter must implement an interface named Interface.
It is allowed to indicate several types of bounds at the same time. In this case, the list of bounds is comma-separated, for example
where T : class, IEquatable, new()
Here 3 bounds are set simultaneously:
- the argument of type T must be of a reference type (class);
- an argument of type T must implement the IEquatable interface;
- an argument of type T must contain a default constructor – new().
⇑
2. The constraint of reference type: where T: class
A reference type constraint is applied when it is necessary to prohibit the use of value types (int, float, double, etc.) as a type argument. More details about the specifics of reference types are described here.
In this case, the constraint on the argument of type T could be:
- any class from the .NET Framework Standard Library or a third-party class;
- any self-developed program class.
⇑
3. An example explaining the use of the class reference type constraint
Let the class RefTypes<T> be given, in which the type T is bounded by the keyword class
// A class in which type T can only be a reference type class RefTypes<T> where T : class { // ... }
This means that the argument of type T can be any reference type from the .NET library, such as String or System.IO.BinaryReader.
// works because String is a reference type RefTypes<String> ref1 = new RefTypes<String>(); // works as the type System.IO.BinaryReader is a reference type RefTypes<System.IO.BinaryReader> ref3 = new RefTypes<System.IO.BinaryReader>();
Also, the argument of type T can be any type implemented in the program. If the program declares a class, interface and structure
// Custom class class MyClass { // ... } // Custom interface interface MyInterface { void Print(); } // Custom structure struct MyStruct { // ... }
then it is allowed to create an instance of the class with arguments of the types MyClass and MyInterface, since they refer to the arguments of the reference type
// this works as MyClass is a self-designed class RefTypes<MyClass> obj = new RefTypes<MyClass>(); // this works because MyInterface is a reference type. RefTypes<MyInterface> mi = new RefTypes<MyInterface>();
But when you try to create an instance of type MyStruct or int (or other value type), the compiler will generate an error
// Attempting to instantiate the RefTypes class with an argument of type int RefTypes<int> rt1 = new RefTypes<int>(); // compilation error RefTypes<Int32> rt2 = new RefTypes<int>(); // also compile error // compilation error because MyStruct is a value type structure RefTypes<MyStruct> ms = new RefTypes<MyStruct>();
⇑
4. Constraint of type where T : struct. Features of use
The constraints of the value types of struct are used to disallow the use of reference types as type arguments. More details about value types and reference types are described here. This restriction is the opposite of the class restriction (see previous paragraphs). The following types are allowed in the struct type constraint:
- structural types such as int, float, char, double and others, as well as their synonyms Int32, Boolean, Char, Single, Double and others;
- custom structured types (structures) that are declared using the struct keyword.
⇑
5. Example explaining the use of struct constraint
Let the following declaration of the class ValTypes<T> be given in which the generic type T is bounded by the keyword struct
// A class in which the type T can only be a value type class ValTypes<T> where T : struct { // ... }
With such a declaration, it is allowed to declare instances of the ValTypes<T> class, in which any value type, such as int or double, acts as type arguments
// Declaring instances for type int (Int32) ValTypes<int> obj1 = new ValTypes<int>(); ValTypes<Int32> obj2 = new ValTypes<int>(); // Declaring instances for type double (Double) ValTypes<double> obj3 = new ValTypes<double>(); ValTypes<Double> obj4 = new ValTypes<Double>();
This is because the types int, double, float and others, as well as their synonyms (Int32, Double, Single …), are significant types – they are structures.
You can also declare instances of custom structures or structures from the .NET Framework library.
If the program declares the structure MyStruct
// A custom structure in the program struct MyStruct { // ... }
then the type of this structure can be applied as an argument of type T when declaring an instance of ValTypes<T>
ValTypes<MyStruct> ms = new ValTypes<MyStruct>();
However, if you try to declare an instance of the ValTypes<T> class with any reference type, you will get a compile-time error.
// Custom class in the program class MyClass { // ... } ... // Compile error ValTypes<MyClass> obj = new ValTypes<MyClass>();
The same goes for classes from the .NET Framework library
// The String class has a reference type ValTypes<String> obj = new ValTypes<String>(); // compilation error // StreamReader class is also of reference type ValTypes<System.IO.TextReader> obj = new ValTypes<System.IO.TextReader>(); // compilation error
This compiler response is provided by the struct constraint when declaring the ValTypes<T> class.
⇑
Related topics
- Generics. Basic concepts. Generic classes and structures
- Constraints on the constructor, base class and interface
- Constraints in generic methods and delegates. Examples. Applying constraints for multiple types
⇑