Skip to content

07 - Generics

Generics - <T>

  • Generics introduce to the .NET Framework the concept of type parameters, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.
  • Generics are also known as parametrized types or parametric polymorphism.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Declare the generic class.
public class GenericList<T> {
    public void Add(T input) {  
        // Do nothing  
    }
}

class TestGenericList {
    private class ExampleClass { }
    static void Main() {
        // Declare a list of type int.
        GenericList<int> list1 = new GenericList<int>();
        list1.Add(1);

        // Declare a list of type string.
        GenericList<string> list2 = new GenericList<string>();
        list2.Add("");

        // Declare a list of type ExampleClass.
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
        list3.Add(new ExampleClass());
    }
}

Generics - Constraints 1

  • where T: struct
    • The type argument must be a non-nullable value type. Implies new().
  • where T: class
    • The type argument must be a reference type. This constraint applies also to any class, interface, delegate, or array type. Must be non-nullable.
  • where T: class?
    • Reference type, can be nullable.
  • where T: notnull
    • T must be a non-nullable type
  • where T: unmanaged
    • The type argument must not be a reference type and must not contain any reference type members at any level of nesting.
  • where T: new()
    • The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
  • where T: <base class name>
    • The type argument must be or derive from the specified base class. T must be a non-nullable reference type derived from the specified base class.
  • where T: <base class name>?
    • The type argument must be or derive from the specified base class. T may be either a nullable or non-nullable type derived from the specified base class.
  • where T: <interface name>
    • The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. T must be a non-nullable type that implements the specified interface.
  • where T: <interface name>?
    • The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. T may be a nullable reference type, a non-nullable reference type, or a value type. T may not be a nullable value type.
  • where T: U
    • The type argument supplied for T must be or derive from the argument supplied for U. In a nullable context, if U is a non-nullable reference type, T must be non-nullable reference type. If U is a nullable reference type, T may be either nullable or non-nullable.

Generics - Constraints 2

Some of the constraints are mutually exclusive.

  • All value types must have an accessible parameterless constructor.
  • The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint.
  • The unmanaged constraint implies the struct constraint.
  • The unmanaged constraint cannot be combined with either the struct or new() constraints.


Unmanaged type

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool
  • Enum, pointer, struct that contains only fields of unmanaged types

Generics - Benefits

  • Reusability with generics means that you will be able to create a method or a class that can be reused with different types in several places.
  • Type safety and better performance both come together because at compile time the C# compiler will give errors and not compile whenever it knows there will an unsafe casting, and at runtime there will be no casting from type to type, it will be handled naturally.
  • No Boxing/Unboxing - Boxing/Unboxing are costly operations, and it is always better to not rely on them heavily in your code.
  • Why we always use the letter T - it is because by convention it refers to the word Type. You can use whatever valid letter or word you like for generics. The naming rules is no different than for naming classes.