I’ve seen many posts about generics in C# lately. And while they cover basics pretty well, most of them doesn’t contain information about constraints which are things that causing generics to be really fun.
101
Lets start with basics in case you’re not familiar with generics at all. When you think about generics in C#, one of first things you should think of are collections. For example let’s take dictionary, because it does have two generic types. Class definition for basic dictionary in C# looks like that.
1 2 3 4 |
public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, ISerializable, IDeserializationCallback { //Stuff... } |
Our main concern are values between angle brackets called TKey and TValue. Long story short, because I don’t want to bore you with basics, they are kind of placeholders for types. You could create dictionary with int, string, long or even some complex type(anything you want) as a key and you could use anything as a value too. Doing that mens you can store pretty much anything in collection but you can’t do anything but Dictionary stuff inside of it because it doesn’t know anything about stored objects, their fields and/or methods.
Interfaces and base classes
One of the basic constraint you can introduce to your generic classes are interface constraints along with base classes constraint. They’re ensuring that your T placeholder will implement interface or derive from base class that we provide in constraint (you’ll see how in a minute).
But without any contraint we could just put ANYTHING in T. Why should we event consider using constraints and making our generic class/method less useful? Basic reason for using constraints in our code is telling our generic class/method what can we do with our T. By constraining our generic TList to implement interface IList<TElements> we’re telling “you can do IList interface stuff on TList you’ll receive” to our code.
Let’s see some code then. Our scenario is pretty simple. We have ICanValidate interface in out code, it have one geter called IsValid that (wait for it!) … validates objects. We want to write extension method for ICollection interface that will add our ICanValidate objects to said collection only after validation returns true and returns said object or null depending on validation and adding result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public interface ICanValidate { bool IsValid { get; } } public static class ICollectionExtensions { public T AddIfValid<T>(this ICollection<T> collection, T item) where T : ICanValidate { if (item.IsValid) { collection.Add(item); return item; } else return null; } } |
This is pretty simple but… code above does not compile. Why?
Class/Struct
Our method want to return null while returning T which could be anything at all that implements interface ICanValidate. For example one of those two types:
1 2 3 4 5 6 7 8 9 |
public class TypeA : ICanValidate { public bool IsValid => true; } public struct TypeB : ICanValidate { public bool IsValid => true; } |
And because TypeB is a struct and just can’t be null. Our generic method will not compile because it still doesn’t know if it will be possible to return null.
But we have two solutions for problems like that. First is using class/struct contraint and adding it to our existing interface contraint. IMPORTANT: class/struct constraints must go at the beggining of our constraints. Our method signature would look just like that:
1 |
public static T AddIfValid<T>(this ICollection<T> collection, T item) where T : class, ICanValidate |
But what about TypeB now? Do we need to write another method for structs implementing our interface? Well, I don’t know what you’re trying to achieve but if you would want to return or whatever default value your struct have you could return default(T) in your method. It provides null for reference types or default values for value types.
1 2 3 4 5 6 7 8 9 10 |
public static T AddIfValid<T>(this ICollection<T> collection, T item) where T : ICanValidate { if (item.IsValid) { collection.Add(item); return item; } else return default(T); } |
New()
But let’s say we changed our mind and just don’t want to return default(T) anymore. Instead we want to return new instance of T. And there is constraint for that called new(). It constrains our T to have public parameterless constructor which we can use in our generic method and needs to be inserted last in our list of constraints. Just like that:
1 2 3 4 5 6 7 8 9 10 |
public static T AddIfValid<T>(this ICollection<T> collection, T item) where T : ICanValidate, new() { if (item.IsValid) { collection.Add(item); return item; } else return new T(); } |
But what if we need parameters to create new instance of our object? For example we want to copy some values from old instance that didn’t pass validation? Well… we don’t have constraint for that but we can play with generics and interfaces a bit and create interface ICanRebuild that promises method From(T item) that will handle old object and extracts interesting values from it. I will not go into all the details but you could take a look at example below. It does work and I encourage you to analyze it a bit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public interface ICanValidate { bool IsValid { get; } } public interface ICanRebuild<TFrom> { void RebuildFrom(TFrom item); } public class TypeA : ICanValidate, ICanRebuild<TypeA> { public string SomeString; public bool IsValid => true; public TypeA() { SomeString = string.Empty; } public void RebuildFrom(TypeA item) { this.SomeString = item.SomeString; } } public class TypeB : ICanValidate, ICanRebuild<TypeB> { public int SomeInt; public bool IsValid => true; public TypeB() { SomeInt = 0; } public void RebuildFrom(TypeB item) { this.SomeInt = item.SomeInt; } } public static class ICollectionExtensions { //Take a look how I've passed T to generic interface ICanRebuild<TFrom> public static T AddIfValid<T>(this ICollection<T> collection, T item) where T : ICanValidate, ICanRebuild<T>, new() { if (item.IsValid) { collection.Add(item); return item; } else { var newItem = new T(); newItem.RebuildFrom(item); return newItem; } } } |
T : OtherT
There is one more type of constraint but I haven’t found any real uses for it yet. In msdn it’s called T : U but i don’t like this name, it’s not clear enough about what exactly U is. And is in fact just other generic parameter, name came alphabethicaly.
What this constraint do? It ensurest that T is derivded from other T. Like I’ve said, I didn’t found any real uses for that but it is possible to do this:
1 2 3 |
DoSomething<TGrandparent, TParent, TChild, OtherT>(TGrandparent x, TParent y, TChild z) where TGrandparent : class, IDisposable, new() where TParent : TGrandparent, IEnumerable<OtherT> where TChild : TParent, ICloneable |
Why would you like to do something like that? I don’t know, but just remember it’s possible.
Summary
Generics are fun and they’re great for some truly universal extension methods and code reusability. If you didn’t knew much about constraints before reading above post I hope you’re proficient enough to make more possibilities for your methods/classes just by constraining type possibilities for them.