C#. Generics. Bounded types

Generics. Bounded types. General concepts. Bounds on reference type and value type


Contents


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