Use Generics Constraints to Reuse Code and Do Compile Time type checking






4.93/5 (5 votes)
An example of how to use generics with constraints to reuse code and have compile time type validation.
Introduction
Assume you have a class with a functionality you wish to implement for a certain group of types.
Assume you also have a derived class which exposes that functionality too, but this derived class works with only a subset of those types.
So the trivial ways to achieve this are:
- Make the method input parameter type to be object and validate the type belongs to the allowed types before applying the functionality on it.
- Make the method input parameter to be a common interface, that way we ensure the type correctness in the base class, on the derived class we need to override the method and verify the type belongs to the subset of types before applying the functionality on it.
- Make the method a generic method, which will also require type validation code on both base and derived classes.
The way I suggest is using generics with constraints, the advantages of this approach is zero type validation code in both base and derived classes and that type validation is done at compile time for both classes.
Here is link to the documentation on generics constraints: http://msdn.microsoft.com/en-us/library/bb384067.aspx
Using the Code
Let's assume you are coding car warehouses with the following rule set:
Car warehouses can be general, European, German and Japanese.
General car warehouses can store all kind of cars, European warehouses can hold only European cars (including German) and so on.
Here is how I suggest to code this problem:
First let's define the car interfaces:
interface ICar {}
interface IEuropeanCar : ICar {}
interface IGermanCar : IEuropeanCar { }
interface IJapaneseCar : ICar {}
Now let's code the warehouse classes:
class GeneralCarWarehouse<T> where T : ICar
{
private List<ICar> _carsInverntory = new List<ICar>();
public void AddToInventory(T car)
{
_carsInverntory.Add(car);
}
}
class EuropeanCarWarehouse<T> : GeneralCarWarehouse<T> where T : IEuropeanCar
{
}
class GermanCarWarehouse : EuropeanCarWarehouse<IGermanCar>
{
}
class JapaneseCarWarhouse : GeneralCarWarehouse<IJapaneseCar>
{
}
As you can see, the method AddToInventory
is coded only once and does not perform any type validation in its code.
The GeneralCarWarehouse
is a generic class and can be created with any type, but due to the constraint, only types inheriting from ICar
can be used, trying to pass other types will not compile.
In order to define a general warehouse accepting all possible cars, we can create a class: MyWarehouse : GeneralCarWarehouse<ICar>
.
The EuropeanCarWarehouse
is also a generic type, but due to the constraint, it can accept only a subset of cars, those cars, which implement the IEuropeanCar
. Once again, trying to create a concrete EuropeanCarWarehouse
with a type that is not inheriting from the IEuropeanCar
, will result in compile time error.
Finally, the GermanCarWarehouse
can only accept cars implementing the IGermanCar
.
Here is a usage example (which adds a few concrete classes):
class MyEuropeanCarWarehouse : EuropeanCarWarehouse<IEuropeanCar> { }
class BmwCar : IGermanCar { }
class FiatCar : IEuropeanCar { }
class ToyotaCar : IJapaneeseCar { }
class Program
{
static void Main(string[] args)
{
var euroCarWareHouse = new MyEuropeanCarWarehouse();
euroCarWareHouse.AddToInventory(new BmwCar());
euroCarWareHouse.AddToInventory(new FiatCar());
euroCarWareHouse.AddToInventory(new ToyotaCar());
}
}
If you will try to compile this code, you'll receive "a cannot convert error", since the ToyotaCar
is not IEuropeanCar
.
To summarize, the solution above solves the issue presented: Applying the same functionality to a group of types and its subgroups, without overriding the method code and with compile time type validation.