Generics in C# allow you to generalise classes and methods to work with different underlying types. Generics are used in many of the C# collections such as List
, Vector
and Queue
.
Using generics allows the compiler to enforce type-safety.
List
, Vector
etc. are examples of generic classes. It is also possible to define generic methods. An example might be a sort algorithm which can work equally well with integers, strings or any IComparable
objects.
Example
An example of a simple collection is given below:
class GenericArray<T>
{
private readonly T[] _data;
public GenericArray(int size) => _data = new T[size];
public T this[int i]
{
get => _data[i];
set => _data[i] = value;
}
}
class Program
{
static void Main(string[] args)
{
var array = new GenericArray<double>(3);
array[0] = 1.0;
array[1] = 2.0;
array[2] = 3.0;
}
}
Note that we have used the expression body syntax to keep the code concise. Our class GenericArray
takes a type parameter T
which defines the type of object we want to store. We have defined a custom indexer so that we can access the array elements using square brackets.
Constraints
By default, any type can be used as a type parameter. However, where this is not appropriate, constraints can be specified on the type parameters.
For example, suppose our GenericArray
class has a Sort()
function. It would therefore be better if we stipulated that the type parameter implements the IComparable
interface.
This can be achieved by changing the class definition as shown:
class GenericArray<T> where T : IComparable
{
...
}
Variance
From C# 4, type parameters can be marked as covariant or contravariant.
If a type parameter is covariant, you can use a more specialised (i.e. derived) type than the one specified. The out
keyword designates a type parameter as covariant.
If a type parameter is contravariant, you can use a more general type than the one specified (i.e. a superclass). The in
keyword designates a type parameter as contravariant.
Here is an example using the Image
and Bitmap
classes from the System.Drawing
namespace. Note that Bitmap
is derived from Image
.
interface IGettable<out T>
{
T Get();
}
interface ISettable<in T>
{
void Set(T value);
}
class Property<T> : IGettable<T>, ISettable<T>
{
private T _value;
public T Get() => _value;
public void Set(T value) => _value = value;
}
class Program
{
static void Main(string[] args)
{
// Covariance
Property<Bitmap> bitmapProp = new Property<Bitmap>();
IGettable<Image> imageProp = bitmapProp;
// Contravariance
Property<Image> imgProp = new Property<Image>();
SetBitmap(imgProp);
}
static void SetBitmap(ISettable<Bitmap> bitmapProp)
{
bitmapProp.Set(new Bitmap(100, 100));
}
}