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;
}- A class that works with any type, specified when it is used.
- 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.
- 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);
}
}
}- A single method can be generic even inside a non-generic class.
- 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.
- 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}";
}- A generic class or interface can have more than one type parameter, like Dictionary<TKey, TValue>.
- 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.
- 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;
}- Restricting a generic type with 'where', so it must satisfy certain requirements.
- 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.
- 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