Codectionary / Developer documentation / C#

Generics

Generics let you write classes, interfaces, and methods that work with any type while still enforcing type safety at compile time. Instead of writing a separate class for each type you want to support, a generic class like Box<T> can hold any type T, with the compiler catching type mismatches before the code ever runs. Type parameters conventionally use T, or a more descriptive name prefixed with T (TKey, TValue) for clarity.

Read the syntax

A type parameter such as T stands for a concrete type selected when the class or method is used. The compiler checks uses of T without requiring a separate implementation for every type.

where constrains allowable type arguments so generic code can rely on specified capabilities, such as implementing IComparable<T>. default! suppresses a nullability warning; it does not initialize a useful value.

Syntax

class ClassName<T> {\n  // T can be used as a type\n}

Examples

Basic Generic Class

A class that works with any type, specified when it is used.

Box<string> stringBox = new Box<string>();
stringBox.Set("Hello");
Console.WriteLine(stringBox.Get());  // Hello

Box<int> intBox = new Box<int>();
intBox.Set(42);
Console.WriteLine(intBox.Get());  // 42

public class Box<T>
{
    private T _content = default!;

    public void Set(T content) => _content = content;
    public T Get() => _content;
}
  1. A class that works with any type, specified when it is used.
  2. A type parameter such as T stands for a concrete type selected when the class or method is used. The compiler checks uses of T without requiring a separate implementation for every type.
  3. Box<string> stores Hello and Box<int> stores 42 with the same implementation. Call Set before Get because the initial default value is not meaningful.

Generic Methods

A single method can be generic even inside a non-generic class.

int[] numbers = { 1, 2, 3 };
string[] words = { "a", "b", "c" };

Utils.PrintArray(numbers);
Utils.PrintArray(words);

public class Utils
{
    public static void PrintArray<T>(T[] array)
    {
        foreach (T item in array)
        {
            Console.WriteLine(item);
        }
    }
}
  1. A single method can be generic even inside a non-generic class.
  2. A type parameter such as T stands for a concrete type selected when the class or method is used. The compiler checks uses of T without requiring a separate implementation for every type.
  3. PrintArray<T> infers T from each array. Its foreach loop prints the integers, then the strings, without writing two specialized methods.

Multiple Type Parameters

A generic class or interface can have more than one type parameter, like Dictionary<TKey, TValue>.

Pair<string, int> pair = new Pair<string, int>("age", 21);
Console.WriteLine(pair);  // age -> 21

public class Pair<TKey, TValue>
{
    public TKey Key { get; }
    public TValue Value { get; }

    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    public override string ToString() => $"{Key} -> {Value}";
}
  1. A generic class or interface can have more than one type parameter, like Dictionary<TKey, TValue>.
  2. A type parameter such as T stands for a concrete type selected when the class or method is used. The compiler checks uses of T without requiring a separate implementation for every type.
  3. Pair<string, int> fixes the key and value types independently. Its ToString interpolation prints age -> 21.

Generic Constraints

Restricting a generic type with 'where', so it must satisfy certain requirements.

NumberBox<int> box = new NumberBox<int>(10);
Console.WriteLine(box.IsGreaterThan(5));  // true

public class NumberBox<T> where T : struct, IComparable<T>
{
    public T Value { get; }
    public NumberBox(T value) => Value = value;

    public bool IsGreaterThan(T other) => Value.CompareTo(other) > 0;
}
  1. Restricting a generic type with 'where', so it must satisfy certain requirements.
  2. A type parameter such as T stands for a concrete type selected when the class or method is used. The compiler checks uses of T without requiring a separate implementation for every type.
  3. The constraint allows CompareTo on Value. NumberBox<int>(10) compared with 5 returns True; an unsupported type argument would fail type checking.

Best practices

  • Use generics whenever a class or method's logic is the same regardless of type, to avoid duplicating code for each specific type
  • Follow C# naming conventions for type parameters: T for a single general type, TKey/TValue for a key-value pair, TResult for a return type
  • Use generic constraints (where T : ...) when your generic code needs to call methods specific to a certain family of types
  • Prefer generics over using object and manual casting - generics catch type errors at compile time instead of causing an InvalidCastException at runtime

Runtime & versions

.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

C# language reference

Specifications & further reading