Generics was introduced in .NET with version 2.0. One feature that is very useful but rarely used is the constraints for Generics. Constraints allows the developer to limit the type that is used instead of any object. Let us try to understand this with a sample. Lets assume that we have a in memory cache class for a certain type of object which we will simulate in the sample using a dictionary. Lets look at the code sample
class MyCache<K, T>
{
Dictionary<K, T> _store = new Dictionary<K ,T>();
void CacheItem(K key, T obj)
{
_store[key] = obj;
}
T GetFromCache (K key)
{
if (_store.ContainsKey(key))
return _store[key];
else
return default(T);
}
}
Generics constraint for Reference Type
The above class looks pretty simple and easy to use. Now lets assume that we want to cache classes that are reference types and this MyCache class should not be use with any struct or value types. How can we do that? Here is how ...
class MyCache<K, T> where T: class
This 'where' keyword is telling the compiler that MyCache type can by applied on the reference types only.
Generics constraint for Value Type
Similarly we would use the following syntax for structs
class MyCache<K, T> where T: struct
Generics constraint for a specific interface
What if we have a specific requirement for the type to be disposable? For example when cache is disposed we want all the items that are in the cache to be disposed as well. If we had no constraints in MyCahe class then we would have to write
public void Dispose()
{
foreach (K key in _store.Keys)
{
IDisposable d = _store[key] as IDisposable;
if (d != null) d.Dispose();
}
}
Observe that we are casting to disposable type and then disposing the object. What if someone had used a type that does not implement IDisposable? Lets try to impose a constraint that says the the type T must be disposable.
class MyCache<K, T> : IDisposable where T:IDisposable
... and our dispose code should look like this ...
public void Dispose()
{
foreach (K key in _store.Keys)
{
_store[key].Dispose();
}
}
Please observe that since we have specified that the type T is a IDisposable type we can directly call the Dispose method.
Generics constraint on multiple types
Now suppose that I have a class named EntityBase that we are using for all our entity classes and the functionality of the MyCache to apply to the entity classes only. How do we tell generics to restrict MyCache to classes that derive from EntityBase and implements IDisposable? This is how ...
class MyCache<K, T> : IDisposable
where T:EntityBase, IDisposable
In the above code we are saying the that the type T must be subclass of EntityBase class and must be disposable. Please note that we would also be able to call methods and properties of the EntotyBase class directly without explicit casting as we have already shown in the disposable example.
Generics constraint on multiple items
What if we wanted the the key to always a value type? Let try to add that to our definition.
class MyCache<K, T> : IDisposable
where K:struct
where T:EntityBase, IDisposable
Specifying a default constructor
Sometimes we might want to write code like new T() to instantiate a variable of type T. This will not work if the class do not have a default constructor. In order to make sure that T has such a default constructor we would write ...
class MyCache<K, T> : IDisposable
where K:struct
where T:EntityBase, IDisposable, new()
The new() keyword will make sure that the T has such a default constructor, otherwise it will not compile.
An interesting tip : Aliasing
We can also use aliases to use generic types in our classes. Sometimes too much generic expression makes the code look bad with lots of <> and <>. This can be avoided via aliasing. You can give a alias or name to a certain generic type and use it from the class. See example below
using StringQueue = System.Collections.Generic.Queue<string>;
Here we are making a generic Queue class bound with string to be named as StringQueue which later used in code like this ...
StringQueue queue = new StringQueue();
queue.Enqueue("Sometext");
Fun, isn't it?
The Last Word
Unfortunately the above feature of generics is usually overlooked and rarely used in code. Better OO design can result from using these features. Lets try to use this when we design object model in our daily life.
