User Tools

Site Tools


Generics in C#

There are two forms of generics in C#:

  • generic types (classes, interfaces, delegates, and structures)
  • generic methods


  • A type parameter is a placeholder for a type.
    • T in List<T>
  • A type argument is a type specified when using the generic. It has to be known at compile time.
    • int in List<int>
  • An unbound generic type does not have any type arguments specified.
    • List<T>
  • A constructed generic type has its type arguments specified.
    • List<int> (constructed generic collection type)
    • Converter<string, double> (constructed generic delegate type)

The JIT creates different code for each constructed generic type with a value type argument but it shares the generated code for all the constructed types that use a reference type as the type argument.

Generic methods

Generic methods are usually used with non-generic types. For example, the Array class has a bunch of generic methods:

public static int BinarySearch<T>(T[] array, T value);
public static int BinarySearch<T>(T[] array, T value, IComparer<T> comparer);
public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter);
public static bool Exists<T>(T[] array, Predicate<T> match);

Generic classes also can contain generic methods. For example, the List<T> class has a generic method ConvertAll<U>:

// T is the type parameter of the List<T> class
// TOutput is the type parameter of the generic method ConvertAll<U>
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter);
// A concrete type for converting a list of strings to a list of doubles could look like this:
public List<double> ConvertAll<double>(Converter<string, double> converter);

Example: Convert strings to doubles using a List<T>'s generic method ConvertAll<U>:

// Create and populated a list of strings.
var list = new List<string> { "8.34", "2.1", "2341.889" };
// Call a generic method ConvertAll.
List<double> doubles = list.ConvertAll<double>(s => Convert.ToDouble(s));


Type constraints give you more control over which type arguments are considered valid when creating custom generic types and methods. Thanks to that you can call methods on instances of the type parameter, or create new instances, or make sure you only accept reference types.

Types of constraints:

  • Reference type constraints (T : class)
    • ensures that the type argument is a reference type e.g. class, interface, array, string, or delegate
    • must be the first constraint for the type parameter
  • Value type constraints (T : struct)
    • ensures that the type argument is a value type e.g. struct, int, Guid, or enum, but it excludes nullable types
    • comparisons using == and != are prohibited
  • Constructor type constraints (T : new())
    • checks if the type argument has a parameterless constructor
    • must be the last constraint for the type parameter
    • useful when creating a factory class
    • applies to: value types; nonstatic, nonabstract classes without any explicitly declared constructors; nonabstract classes with an explicit public parameterless constructor
  • Conversion type constraints
    • allows you to specify another type that the type argument must be implicitly convertible to via an identity, reference, or boxing conversion
    • allows you to call methods on instances of the type parameter
    • it is possible to specify that one type argument be convertible to another (type parameter constraint)
    • multiple interfaces can be specified
    • these type constraints are also called base class constraints and interface constraints which specify that the type parameter must subclass or implement a particular class or interface


class MyBaseClass {}
interface Interface1 {}
// MyClass<T,U> requires T to derive from MyBaseClass and implement Interface1. 
// It also requires U to provide a parameterless constructor.
class MyClass<T,U> where T : MyBaseClass, Interface1 
                   where U : new()

Example: Conversion type constraints:

class MyClass<T> where T : Stream { ... }
// identity conversion
struct MyClass<T> where T : IDisposable { ... }
// reference conversion
class MyClass<T> where T : IComparable<T> { ... }
// boxing conversion
// type parameter constraint
class MyClass<T,U> where T : U  { ... }
// reference conversion 

Example: Combine constraints:

class Sample<T> where T : class, IDisposable, new()
class Sample<T> where T : struct, IDisposable
// Each list of type parameter constraints needs its own introductory where.
class Sample<T,U> where T : Stream where U : IDisposable
// Although U is a value type it can derive from T (which is a reference type) when T is an object
// or an interface that U implements.
class Sample<T,U> where T : class where U : struct, T


  • The EqualityComparer<T> class implements the IEqualityComparer<T> interface.
  • The Comparer<T> class implemets the IComparer<T> interface.

Example: A helper class to store user's settings in the UWP's local storage:

class SettingsHelper
    public int CurrentUserId
        get { return GetValue<int>("CurrentUserId"); }
        set { SetValue("CurrentUserId", value); }
    private T GetValue<T>(string key)
        ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
        if (settings.Values.ContainsKey(key))
            return (T)settings.Values[key];
            return default(T);
    private void SetValue<T>(string key, T val)
        T currentValue = GetValue<T>(key);
        if (!EqualityComparer<T>.Default.Equals(currentValue, val))
            ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
            settings.Values[key] = val;

Static fields

Each closed type has its own set of static fields. The same applies for static initializers and static constructors:

class MyCounter<T> { public static int Count; }
Console.WriteLine(++MyCounter<int>.Count);     // 1
Console.WriteLine(++MyCounter<int>.Count);     // 2
Console.WriteLine(++MyCounter<string>.Count);  // 1
Console.WriteLine(++MyCounter<object>.Count);  // 1

For types with multiple generic parameters each different list of type arguments counts as a different closed type.


For information on using reflection with generics refer to the Generics subsection of the Reflection notes.

notes/csharp/generics.txt · Last modified: 2020/08/26 (external edit)