ZetCode

C# generics

last modified October 15, 2026

In this article we show how to define and use generics in C#. In generic programming, we use types as parameters to define other types and methods.

Generics were added in C# 2.0.

Generics are classes, structures, interfaces, and methods that have placeholders (type parameters) for one or more of the types that they store or use. A generic collection class might use a type parameter as a placeholder for the type of objects that it stores.

Type parameters are provided after the class, structure, interface, or method name in angle brackets. This syntax tells the compiler that such a name is used in the definition.

By convention, the names of type parameters start with a capital letter T, such as T, TKey, or TValue.

In effect, we delay the specification of the data type until it is actually used in the program. Generics enable us to write code that works with a variety of types without repeating the code for each type.

void Swap<T>(ref T lhs, ref T rhs)

We have a definition of a generic method. The T parameter is later replaced with a concrete type such as int or string at the calling side.

Generics are abstract blueprints; they cannot be used as-is. When we use a generic class or method, we have to instantiate it with a specific data type that is substituted for the matching placeholder.

.NET provides a large set of interfaces and classes in the System.Collections.Generic namespace for implementing generic collections.

Generics provide the following advantages:

One generic method/class definition can be used for various types, thus reducing the amount of code needed. Generics provide type safety; the compiler checks at compile time that correct types are being provided. Generic types provide better performance because they reduce the need for boxing, unboxing, type checking, and typecasting of variables or objects.

C# generic method

In the following example, we define a generic Swap method.

Program.cs
int x = 10;
int y = 20;

char a = 'x';
char b = 'y';

Swap<int>(ref x, ref y);
Swap<char>(ref a, ref b);

Console.WriteLine($"x = {x}, y = {y}");
Console.WriteLine($"a = {a}, b = {b}");

void Swap<T>(ref T lhs, ref T rhs)
{
    T temp = lhs;
    lhs = rhs;
    rhs = temp;
}

The Swap method exchanges the values of two variables. There is one definition of a generic Swap method, but it is used for two different types.

Swap<int>(ref x, ref y);
Swap<char>(ref a, ref b);

We call the Swap method for integer values and char values. The type specification for the method is optional since the compiler can infer the types from the arguments.

void Swap<T>(ref T lhs, ref T rhs)
{
    T temp = lhs;
    lhs = rhs;
    rhs = temp;
}

Capital T is used as a placeholder, which is replaced with a specific type when the method is used.

$ dotnet run
x = 20, y = 10
a = y, b = x

Next we have a generic Debug method. It prints the type and the value of the passed generic argument.

Program.cs
string s = "falcon";
bool b = true;
int i = 42;
float f = 4.4f;
double d = 2.0;

Debug<string>(s);
Debug<bool>(b);
Debug<int>(i);
Debug<float>(f);
Debug<double>(d);

void Debug<T>(T arg)
{
    Console.WriteLine($"[{typeof(T).Name}]: {arg}");
}

To get the type of the type parameter, we use the typeof operator. Unlike in Java, the type arguments are available at runtime in C#. The Name property returns the name of the .NET type.

$ dotnet run
[String]: falcon
[Boolean]: True
[Int32]: 42
[Single]: 4.4
[Double]: 2

In the next example, we create a method that randomly shuffles a list.

Program.cs
var rng = new Random();

var vals = new List<int> { 1, 2, 3, 4, 5, 6 };
var words = new List<string> { "sky", "blue", "war", "toy", "tick" };

Shuffle<int>(vals);
Shuffle<string>(words);

foreach (var e in vals)
{
    Console.Write($"{e} ");
}

Console.WriteLine("\n-----------------------");

foreach (var e in words)
{
    Console.Write($"{e} ");
}

Console.WriteLine();

void Shuffle<T>(IList<T> vals)
{
    int n = vals.Count;

    while (n > 1)
    {
        n--;
        int k = rng.Next(n + 1);

        T value = vals[k];

        vals[k] = vals[n];
        vals[n] = value;
    }
}

We shuffle a list of integers and a list of words. The method implements the Fisher-Yates shuffle algorithm.

$ dotnet run
6 2 3 1 4 5 
-----------------------
toy war sky blue tick 
$ dotnet run
2 5 1 4 3 6 
-----------------------
war sky blue toy tick 

Since .NET 8, arrays and spans can be shuffled with the built-in Random.Shuffle method.


Next we create methods for list access.

Program.cs
var vals = new List<int> { 1, 2, 3, 4, 5, 6 };
var words = new List<string> { "sky", "blue", "war", "toy", "tick" };

Console.WriteLine(First(vals));
Console.WriteLine(Second(vals));
Console.WriteLine(Last(vals));
Console.WriteLine(SecondLast(vals));

Console.WriteLine("--------------");

Console.WriteLine(First(words));
Console.WriteLine(Second(words));
Console.WriteLine(Last(words));
Console.WriteLine(SecondLast(words));

T First<T>(IList<T> items) => items[0];
T Second<T>(IList<T> items) => items[1];
T Last<T>(IList<T> items) => items[^1];
T SecondLast<T>(IList<T> items) => items[^2];

In the example, we have four generic methods to get the first, second, last, and last but one elements of the list.

Console.WriteLine(First(vals));
Console.WriteLine(Second(vals));
...

The compiler infers the type of the parameter based on the value we pass to the method; therefore, specifying type (e.g. First<int>) is optional.

$ dotnet run
1
2
6
5
--------------
sky
blue
tick
toy

C# generic delegate

In the following example, we define a generic delegate. The generic methods use generic math, which was introduced in C# 11 and .NET 7.

Program.cs
using System.Numerics;

var mv1 = new ModifyVal<int>(Inc);
var mv2 = new ModifyVal<int>(Dec);
var mv3 = new ModifyVal<float>(Inc);
var mv4 = new ModifyVal<decimal>(Dec);

Console.WriteLine(mv1(7));
Console.WriteLine(mv2(7));
Console.WriteLine(mv3(8.5f));
Console.WriteLine(mv4(8.5m));

T Inc<T>(T val) where T : INumber<T> => val + T.One;
T Dec<T>(T val) where T : INumber<T> => val - T.One;

delegate T ModifyVal<T>(T val);

We define a generic delegate ModifyVal; it is used to refer to two generic methods: Inc and Dec.

T Inc<T>(T val) where T : INumber<T> => val + T.One;

The Inc is a generic method; it takes a generic parameter and returns a generic value. The INumber<T> constraint allows us to use arithmetic operators on the generic type. The static T.One property returns the value 1 of the given numeric type. All the checking is done at compile time.

delegate T ModifyVal<T>(T val);

This is the definition of the generic delegate.

$ dotnet run
8
6
9.5
7.5

C# generic class

Next, we define a generic class.

Program.cs
var ds1 = new DataStore<string>();
ds1.Data = "an old falcon";
Console.WriteLine(ds1);

var ds2 = new DataStore<int>();
ds2.Data = 23;
Console.WriteLine(ds2);

var ds3 = new DataStore<bool>();
ds3.Data = false;
Console.WriteLine(ds3);

class DataStore<T>
{
    public T? Data { get; set; }

    public override string ToString()
    {
        return Data?.ToString() ?? string.Empty;
    }
}

The example stores a value in a generic property. The property is declared as nullable, because it has no value until it is set.

$ dotnet run
an old falcon
23
False

C# generic FindAll

In the next example, we define a FindAll list extension method.

ExtensionMethods.cs
public static class ExtensionMethods
{
    public static List<T> FindAll<T>(this List<T> vals, List<Predicate<T>> preds)
    {
        List<T> data = new List<T>();

        foreach (T e in vals)
        {
            bool pass = true;

            foreach (Predicate<T> p in preds)
            {
                if (!(p(e)))
                {
                    pass = false;
                    break;
                }
            }

            if (pass) data.Add(e);
        }

        return data;
    }
}

The FindAll method returns list elements that satisfy all the specified predicates.

public static List<T> FindAll<T>(this List<T> vals, List<Predicate<T>> preds)

The FindAll method takes a list of generic predicate functions as a parameter. It returns a filtered generic list.

Program.cs
var preds = new List<Predicate<int>>();
preds.Add(e => e > 0);
preds.Add(e => e % 2 == 0);

var vals = new List<int> {-3, -2, -1, 0, 1, 2, 3, 4};
var filtered = vals.FindAll(preds);

foreach (var e in filtered) 
{
    Console.WriteLine(e);
}

Console.WriteLine("---------------------");

var words = new List<string> {"sky", "wrath", "wet", "sun", "pick", "who", 
    "cloud", "war", "water", "jump", "ocean"};

var preds2 = new List<Predicate<string>>();
preds2.Add(e => e.StartsWith("w"));
preds2.Add(e => e.Length == 3);

var filtered2 = words.FindAll(preds2);

foreach (var e in filtered2) 
{
    Console.WriteLine(e);
}

We define two lists: an integer list and a string list. From the integer list, we filter out all positive even values. From the string list, we get all words that start with 'w' and have three letters.

$ dotnet run 
2
4
---------------------
wet
who
war

C# generic constraints

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type or method.

ConstraintDescription
where T : structT must be a non-nullable value type
where T : classT must be a reference type
where T : notnullT must be a non-nullable type
where T : unmanagedT must be a non-nullable unmanaged type
where T : new()T must have a public parameterless constructor
where T : BaseClassT must be or derive from the specified base class
where T : IInterfaceT must implement the specified interface
where T : UT must be or derive from the type parameter U

The table lists the most common constraints.

Program.cs
var shelter = new Shelter<Cat>();
shelter.Add(new Cat("Lucky"));
shelter.Add(new Cat("Missy"));
shelter.Show();

var kennel = new Shelter<Dog>();
kennel.Add(new Dog("Rex"));
kennel.Show();

// var garden = new Shelter<Flower>(); // compile-time error

interface IAnimal
{
    string Name { get; }
    string Sound();
}

record Cat(string Name) : IAnimal
{
    public string Sound() => "meow";
}

record Dog(string Name) : IAnimal
{
    public string Sound() => "woof";
}

record Flower(string Name);

class Shelter<T> where T : IAnimal
{
    private readonly List<T> animals = [];

    public void Add(T animal) => animals.Add(animal);

    public void Show()
    {
        foreach (var animal in animals)
        {
            Console.WriteLine($"{animal.Name} says {animal.Sound()}");
        }
    }
}

In the example, we define a Shelter class that restricts the T type to types implementing the IAnimal interface. Thanks to the constraint, we can access the Name property and call the Sound method inside the generic class.

// var garden = new Shelter<Flower>(); // compile-time error

The Flower type does not implement IAnimal; if we uncomment the line, the compiler reports the CS0311 error.

$ dotnet run
Lucky says meow
Missy says meow
Rex says woof

In the next example, we use an interface constraint to write a generic Max method.

Program.cs
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("falcon", "eagle"));
Console.WriteLine(Max(new DateOnly(2026, 1, 1), new DateOnly(2025, 12, 31)));

T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

The method works with all types that implement the IComparable<T> interface. We compare integers, strings, and dates.

$ dotnet run
7
falcon
1/1/2026

C# generic list iterator

The following example creates a generic iterator.

Program.cs
var words = new List<string> { "sky", "cloud", "rock", "war", "web" };
var it = CreateIterator(words);

string? e;

while ((e = it()) != null)
{
    Console.WriteLine(e);
}

Iterator<T> CreateIterator<T>(IList<T> data) where T : class
{
    var i = 0;
    return delegate { return (i < data.Count) ? data[i++] : null; };
}

public delegate T? Iterator<T>() where T : class;

An iterator uses an anonymous method. Each call returns the next element of the list; when there are no more elements, it returns null. The class constraint is needed, because only reference types can be null. The anonymous method captures the i variable, which keeps the current position.

$ dotnet run
sky
cloud
rock
war
web

C# generic list forEach

The following example creates a generic forEach method for a list container.

Program.cs
Action<int> show = Console.WriteLine;

var vals = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
forEach(vals, show);

Console.WriteLine("--------------------------");

var words = new List<string> { "sky", "cloud", "rock", "water" };
forEach(words, Console.WriteLine);

Console.WriteLine("--------------------------");

var users = new List<User> 
{
    new ("John Doe", "gardener"),
    new ("Roger Roe", "driver"),
};

forEach(users, Console.WriteLine);

void forEach<T>(IList<T> vals, Action<T> fn)
{
    foreach (T val in vals)
    {
        fn(val);
    }
}

record User(string Name, string Occupation);

The generic forEach method goes over integer, string, and User elements of three list containers.

void forEach<T>(IList<T> vals, Action<T> fn)

The forEach method takes a generic list and a generic Action delegate as parameters.

$ dotnet run
1
2
3
4
5
6
7
--------------------------
sky
cloud
rock
water
--------------------------
User { Name = John Doe, Occupation = gardener }
User { Name = Roger Roe, Occupation = driver }

C# generic dictionary ForEach

In the next example, we create a generic ForEach method for a dictionary.

Program.cs
var domains = new Dictionary<string, string>
{ 
    {"sk", "Slovakia"}, 
    {"ru", "Russia"},
    {"de", "Germany"},
    {"no", "Norway"}
};

domains.ForEach((k, v) => Console.WriteLine($"{k} - {v}"));

var vals = new Dictionary<int, string>
{ 
    {1, "coin"}, 
    {2, "pen"},
    {3, "pencil"},
    {4, "book"}
};

vals.ForEach((k, v) => Console.WriteLine($"{k} - {v}"));

static class DictionaryExtension
{
    public static void ForEach<T1, T2>(this Dictionary<T1, T2> dict,
        Action<T1, T2> fn) where T1 : notnull
    {
        foreach (KeyValuePair<T1, T2> pair in dict)
        {
            fn(pair.Key, pair.Value);
        }
    }
}

The ForEach method is defined as an extension method. We use the foreach loop to go over the pairs of the dictionary and call the Action delegate for each of them. The notnull constraint is required, because dictionary keys cannot be null.

$ dotnet run
sk - Slovakia
ru - Russia
de - Germany
no - Norway
1 - coin
2 - pen
3 - pencil
4 - book

Source

Generic classes and methods

In this article we have covered generic programming in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.