Generic Types
Learn about classes that accept type parameters.
We'll cover the following...
Introduction
Consider the following Holder class:
public class Holder
{
public string[] Items { get; private set; }
public Holder(int holderSize)
{
Items = new string[holderSize];
}
public override string ToString()
{
string result = "Items inside: ";
foreach (var item in Items)
{
result = result + item + " ";
}
return result;
}
}
It has the Items property, which is string type. The Holder class holds string items. What if we need a similar class that holds integers? The functionality is the same, but the type is different.
We could suggest something like the following:
public class IntHolder
{
// Now, the Items property holds integers
public int[] Items { get; private set; }
public IntHolder(int holderSize)
{
Items = new int[holderSize];
}
public override string ToString()
{
string result = "Items inside: ";
foreach (var item in Items)
{
result = result + item + " ";
}
return result;
}
}
This works. Is this feasible if we want to create such a class for many more types, though? We’d be copying the same code and changing the type of the internal array used to hold the items.
A better approach uses generics.
Generic types
Generics are classes with members whose types are specified during variable declaration and object creation. In other words, instead of having IntHolder or StringHolder, we can have a single class that instantiates as follows:
Holder<int> intHolder = new Holder<int>(); // Holder class holds integers
Holder<string> stringHolder = new Holder<string>(); // Holder class holds strings
With a generic Holder class, the type of the Items property is specified during variable declaration instead of being hard-coded inside the class:
We declare Items to be a type T array. This placeholder is replaced by a concrete type when we create a Holder<T> object, replacing the T with a type we want to use.
In our example, we create two holders, one of type Holder<string> and one of type Holder<int>. The type that goes inside <> is called a type parameter, and it replaces the placeholder, T:
Default values
Remember that the default value for a numeric value-type variable is zero, while a reference-type variable doesn’t point to anything (it’s null). To assign a default value to a T member, we can use the default operator:
public T Name { get; set; } = null; // Incorrect
public T JobPosition { get; set; } = default(T); // Depending on the actual type that will be used, default() will return a default value for this type
Accept multiple type parameters
A generic class can accept multiple type parameters. We don’t have to limit ourselves to the letter T when choosing a placeholder:
public class SystemBootstrapper<A, B>
{
public A FirstParameter { get; set; }
public B SecondParameter { get; set; }
}
Generic methods
Besides generic types, we can create generic methods that accept type parameters: