This is my first post. I hope that it doesn’t suck.
As of .NET 2.0, Microsoft introduced the concept of generics. Generics is a concept that allow you to “template” methods and types such as classes and interfaces in a (generally) type-safe way. Upon compilation, generic type metadata is stored in IL, and JIT’d as you reference the generic method or class with an actual type at runtime. Value types each get their own “copy” of the JIT’d generic code, whereas reference types share a single instance of the code. This is because the generic implementation is identical for reference types – they’re all just pointers.
Anyway, you probably use generics daily, whether or not you know them by name. Ever used List<T>
? IEnumerable<T>
? Dictionary<TKey, TValue>
? Those are pre-built generic classes and interfaces which are included in the .NET libraries.
You can build your own generic classes and methods in C#. Many people do this also daily. Perhaps to create a custom collection or caching method… but, have you ever used generic constraints?
Generic constraints allow you to constrain, or restrict, the types that can be used by your generic classes and methods. This enables you to examine the generic type with certainty about it’s functionality. Why does this matter? It allows you to invoke methods or properties on your type without using reflection.
The syntax is such that at the type or method definition, you add “where T : your constraints here“. An example:
public static T Create<T> where T : new()
The “new()” in this case indicates that whatever type is passed to this method MUST have a parameterless constructor. What does this afford you? You can now new up T in your method!
As a simple example, let us create the static factory method above that serves only to return a newly created instance of the generic type parameter:
/// <summary>
/// Creates an object of Type T.
/// </summary>
/// <typeparam name="T">The Type to Create.</typeparam>
/// <returns>An instantiated Type of T.</returns>
public static T Create<T>() where T : new()
{
return new T();
}
Sweet! Now we can new up stuff like crazy. Let’s define a “Hat” class that represents a hat:
/// <summary>
/// A Hat.
/// </summary>
public class Hat
{
/// <summary>
/// The Size of the Hat.
/// </summary>
public int Size { get; set; }
}
Now let’s new that bad boy up:
static void Main(string[] args)
{
var myHat = Create<Hat>();
}
Voila, we just made a hat with generics and generic constraints! But that’s pretty boring, and you might ask “why not just new up the bloody hat and skip all of that factory stuff?” Good question. We’ll discuss that in the future, but there are many reasons you might wish not to directly instantiate a concrete type in your methods or classes.
Let’s take a more practical example. Let us define the Size property in an interface and make the Hat implement that interface:
/// <summary>
/// A Hat.
/// </summary>
public class Hat : IHaveASize
{
/// <summary>
/// The Size of the Hat.
/// </summary>
public int Size { get; set; }
}
/// <summary>
/// Represents an object that has a Size.
/// </summary>
public interface IHaveASize
{
/// <summary>
/// The Size.
/// </summary>
int Size { get; set; }
}
Now let’s create a generic method that checks if a size is “valid” which we’ll define as being greater than 0.
/// <summary>
/// Determines whether or not Type T has a Valid Size.
/// </summary>
/// <typeparam name="T">The Type.</typeparam>
/// <param name="sizedObject">The object which has a Size.</param>
/// <returns>true if the Size is greater than 0, false otherwise.</returns>
public static bool IsSizeValid<T>(T sizedObject) where T : IHaveASize
{
return sizedObject.Size > 0;
}
Note that we can access the Size property in the IsSizeValid method because of our generic constraint! Now to execute the code:
static void Main(string[] args)
{
var myHat = Create<Hat>();
// We don't have to say IsSizeValid<Hat>(myHat) - the type is inferred by the parameter!
var result = IsSizeValid(myHat);
}
And there you have it. An introduction into generic constraints!