Reading through chapter 2 (see, I’m being good) a new thought about return type covariance occurred to me. This is odd in itself, because I thought I’d exhausted my own supply of ideas around variance (which isn’t the same as knowing everything about it, of course – it just means I don’t expect to have anything new to say).
Just as a reminder, in C# 1 delegates were completely invariant – in order to create a delegate instance from a method group, for instance, the signature had to match exactly. For instance, suppose we had a delegate type declared as:
We couldn’t (in C# 1) use the following method to create an instance of
return new StringBuilder();
Even though a
StringBuilder is always an
object, the signatures don’t match exactly, and it was prohibited.
C# 2 allows this, however, so it’s legal to write:
So far, so good. (Yes, a generic delegate type would be nicer, but I’m avoiding the variance issues with generics for this post. Let’s not make things more complicated than they need to be.) Just to be absolutely clear about this, it’s valid and legal because there’s nothing you can do with
factory now which will break normal type safety. You can call
factory() and be absolutely sure that it will return an
object of some kind (or
null) so it’s as safe as anything else.
Now, what about a delegate type which has a void return type:
You can’t use
CreateStringBuilder as the target of an instance of
Action – C# 2 and even C# 3 completely disallow it. My guess is that the CLR disallows it internally. Why is this? Again, any use of an
Action delegate can’t possibly care about what the target returns, because it’s not declared to return anything.
I strongly suspect that the answer lies in the implementation of the CLR rather than in any deep semantic reason – the CLR probably needs to know whether or not there’s going to be a return value, in order to do appropriate things with the stack. Even so, it seems a bit of a pity, in terms of elegance. I can’t say I’ve ever felt the need for this in real life, and it would be reasonably easy to fake (for up to four parameters) in .NET 3.5 just by writing a converter from Func<X> to Action<X>, Func<X,Y> to Action<X,Y> etc. It niggles a bit though :)