Generic Types are Litter
The short version of this post:
Do not spread generic types around. They are ugly and primitive.
Allow me to elaborate.
Generics are great
Before we got Generics (2004 in Java, 2005 in C#, and 2009 in Delphi) the way to enforce type safety on a collection type was to encapsulate it and use delegation.
class OrderList
{
private ArrayList items = new ArrayList();
public void Add(OrderItem item)
{
items.Add(item);
}
// Plus lots of more delegation code
// to implement Remove, foreach, etc.
}
Nowadays, we can construct our own type safe collection types with a single line of code.
List<orderitem> orders = new List<orderitem>();
That alone is good reason to love generics, and there lies the problem; many developers have come to love their generics a little too much.
Generics are ugly
The expressiveness of generics comes at a price, the price of syntactic noise. That’s natural. We must specify the parameterized types somehow, and the current syntax for doing so is probably as good as anything else.
Still, my feeling when I look at code with generics is that the constructed types don’t harmonize with the rest of the language. They kind of stand out, and are slightly negative for code readability.
OrderList orders = new OrderList();
// as compared to
List<OrderItem> orders = new List<OrderItem>();
This might not be so bad, but when we start to pass the constructed types around they become more and more like litter.
List<OrderItem> FetchOrders()
{
//…
}
double CalculateTotalSum(List<OrderItem> orders)
{
//…
}
void PrintOrders(List<OrderItem> orders)
{
//…
}
Besides being noisy, the constructed types expose implementation decisions. In our case the list of orders is implemented as a straight list. What if we found out that using a hash table implementation for performance reasons would be better? Then we’d have to change the declaration in all those places.
Dictionary<OrderItem, OrderItem> FetchOrders()
{
//…
}
double CalculateTotalSum(Dictionary<OrderItem, OrderItem> orders)
{
//…
}
void PrintOrders(Dictionary<OrderItem, OrderItem> orders)
{
//…
}
Comments redundant.
Generics are primitive
A related problem is that the constructed generic types encourage design that is more procedural than object-oriented. As an example, consider the CalculateTotalSum method again.
double CalculateTotalSum(List&amp;amp;lt;OrderItem&amp;amp;gt; orders)
{
//…
}
Clearly, this method belongs in the List<OrderItem> type. Ideally, we should be able to invoke it using the dot operator.
orders.TotalSum();
// instead of
CalculateTotalSum(orders);
But we cannot make that refactoring. We cannot add a TotalSum method to the List<OrderItem> type. (Well maybe we can if we make it an extension method, but I wouldn’t go there.) In that sense, constructed generic types are primitive types, closed and out of our control.
So we should hide them
Don’t get me wrong. I use generics a lot. But for the previously given reasons I do my best to hide them. I do that using the same encapsulation technique as before, or – at the very least – by inheriting the constructed types.
class OrderList : List<OrderItem>
{
double TotalSum()
{
//…
}
}
So, the rule I go by to mitigate the downsides of generics, is this:
Constructed types should always stay encapsulated. Never let them leak through any abstraction layers, be it methods, interfaces or cl
That is my view on Generics. What is yours?
Cheers!
P.S.
Just to be clear, the beef I have with Generics is with constructed types alone. I have nothing against using open generic types in public interfaces, although that is more likely to be a tool of library and framework developers.