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.

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.

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

    public void Set(T content) => _content = content;
    public T Get() => _content;
}

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

Generic Methods

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

public class Utils
{
    public static void PrintArray<T>(T[] array)
    {
        foreach (T item in array)
        {
            Console.WriteLine(item);
        }
    }
}

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

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

Multiple Type Parameters

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

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}";
}

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

Generic Constraints

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

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;
}

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

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

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Classes & Objects
A class is a blueprint for creating objects, bundling related data (fields/properties) and behavior (methods) together. C# is a fully object-oriented language - every application has at least one class, typically with a Main method as its entry point. An object is a specific instance of a class, created with the new keyword, with its own independent copy of the class's instance data.
Constructors
A constructor is a special method that runs automatically when an object is created with new, used to initialize the object's state. A constructor shares its name with the class and has no return type. C# supports constructor overloading (multiple constructors with different parameter lists) and constructor chaining with this(...), where one constructor calls another in the same class to avoid duplicating initialization logic.
Properties (get/set)
Properties are C#'s idiomatic way to expose class data through accessor-like syntax while still allowing controlled access via get and set. Unlike a plain public field, a property can validate a value before it's set, compute a value on the fly, or restrict access to read-only. Auto-implemented properties (using { get; set; } with no body) provide a concise shorthand when no custom logic is needed.
Access Modifiers
Access modifiers control the visibility of classes, fields, methods, and properties from other parts of a program. C# provides public (accessible from anywhere), private (accessible only within the declaring class - the default for class members), protected (accessible within the class and its subclasses), internal (accessible only within the same assembly/project), and combinations like protected internal and private protected.