Generic interfaces and delegates
In the previous post, I’ve introduced the concept of generics. In this post, we’ll keep looking at generics and we’ll talk a little bit about how we can create generic interfaces and delegates. Without generic interfaces, manipulating a value type through its interface reference would result in boxing (and less compile type safety). The some goes for delegates: with generics, it’s possible to create a delegate which allows a value type instannce to be passed back to a callback method in a type-safe way without any boxing.
There’s not much to say about the syntax used for creating generic interfaces and delegates. So, here’s an of a generic interface:
And here’s an example of a generic delegate (btw, it’s defined by the framework):
Tipically, the compiler will transform a delegate into a class which looks like this:
(And we’ll see why in future posts )
From .NET 4.0 onward, the type arguments of a generic delegate or interface can be classified as contravariant or covariant. In previous versions, generic arguments were always invariant, ie, the generic type parameter couldn’t be changed after being defined. In C#, contravariant type arguments are indicated through the in keyword. It means that the generic type parameter can change from a class to another derived from it. On the other hand, covariant type arguments are indicated in C# through the out keyword and, in this case, a generic type argument can change from a class to one of its base class. An example might help you understand what’s goin here. Suppose we start with this (again, defined by the framework):
In this case, T is contravariant and TResult is covariant. So, if you have the following classes:
Now, if you still haven’t forgotten that Object is the base class of all objects, then from .NET 4.0 onward, you can simply do this:
Notice that there’s no cast there! this is not the easiest example to understand what’s going on, but I really don’t have time for more (sorry). What matters is understanding that fun1 references a method which expects and object and returns na instance of Derived. On the other hand, fun2 expects a method that receives a Base and returns another Base instance. Since you can pass a Base to a method which expects an Object and since you can treat a Derived instan
ce as if it was a Base instance (because Derived extends Base), then the previous code is safe.
Interestingly, the compiler will only be happy with contravariance and covariance if it can infer that there is a conversion between the tpyes being used. That means that variance won’t work for value types because it would require boxing. In practice, this means that this won’t compile:
It’s a pity, but that’s just the way it is. Before ending, there’s still time to say that you should always specify the variance of a type argument when creating generic arguments. This doesn’t introduce any nasty side effects and enables the use of the delegate in more scenarios. If you want to know more about variance, then I recommend reading Eric Lippert’s excellent series on the topic. And that’s it for now. Stay tuned for more.