Let's say I have these classes:
class Animal { }
class Cat : Animal { }
And then I declare a variable like this:
Action<Cat> c;
Now, Action<T>
is contravariant on T
, so I can do this:
void Foo(Animal a) { }
c = Foo;
That makes sense. But when I do this, I get compiler error CS16611:
c = (Animal a) => { };
I can even do this with no error:
Action<Animal> b = (Animal a) => { };
c = b;
What am I missing here?
Super short version: you can tell the compiler which delegate type you want it to use for your expression using casting:
c = (Action<Animal>)(a => { });
Right, that aside..
As madreflection pointed out while I was writing this (thanks mad), the problem is that pure delegates are not contravariant. Contravariance works only for generic delegates of the same generic type.
Consider this list of items:
// Let's start with defining some delegate types:
delegate void AnimalAction(Animal parm);
delegate void MyAction<in T>(T parm);
// Now here are my variables, all working fine:
Action<Animal> actAnimal = (Animal a) => { };
MyAction<Animal> myAnimal = (Animal a) => { };
AnimalAction animalAction = (Animal a) => { };
They all look basically the same, and indeed are all functionally identical. Sadly no matter how similar they look, and no matter the fact that the method void DoNothing(Animal parm) { }
can be assigned to all of them, the compiler will tell you that they're not the same.
So what happens when we have a lambda expression like (Animal a) => { }
? Well all of the above will accept it because the compiler is bright enough to figure out what you're doing.
Now let's change it up:
// New delegate for cats
delegate void CatAction(Cat parm);
// New variables, but these all fail:
Action<Cat> actCat = (Animal a) => { };
MyAction<Cat> myCat = (Animal a) => { };
CatAction catAction = (Animal a) => { };
The compiler objects in all of these cases because it the transitional type of the lambda expression (delegate _expression_type(Animal a)
) is not reference-convertable to the various target delegate types.
You can get around this in a few ways however. You can wrap one delegate in another of compatible type using new Action<Cat>(some_animal_action)
. Or you can simply tell the compiler to make the right type from the beginning, like I put at the top.
So these all work:
c = (Action<Animal>)(a => { });
c = new Action<Animal>(a => { });
c = new Action<Animal>((Animal a) => { });
(But don't do the new
versions, it will probably create a nested delegate.)
Oddly, you can also capture the output in an auto-typed variable using var
. This works because the compiler knows to use Action<...>
and Func<...>
for expression delegates, but only when the delegate's type is not fully specified beforehand. So this works too:
var temp = (Animal a) => { };
// 'temp' is of type Actions<Animal> so this works:
c = temp;