User Tools

Site Tools


notes:csharp:generics

Generics in C#

There are two forms of generics in C#:

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

Terminology

  • 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));

Constraints

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

Example:

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
MyClass<Stream>
 
struct MyClass<T> where T : IDisposable { ... }
// reference conversion
MyClass<SqlConnection>
 
class MyClass<T> where T : IComparable<T> { ... }
// boxing conversion
MyClass<double>
 
// type parameter constraint
class MyClass<T,U> where T : U  { ... }
// reference conversion 
MyClass<Stream,IDisposable>

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

Comparison

  • 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];
        }
        else
            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.

Reflection

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

notes/csharp/generics.txt · Last modified: 2017/03/06 by admin