Reputation: 974
I'm having a hard time to understand why Action<T>
is contravariant and Func<T>
covariant , why should Action<T>
be contravariant and why should Func<T>
be covariant, any guidelines about when to use one and when to use the other .
Upvotes: 0
Views: 269
Reputation: 144196
If Action<T>
were covariant you would be able to do this:
Action<string> sa = s => { Console.WriteLine(s[0]); }
Action<object> oa = sa;
oa(1);
and pass an int
to an action with a string parameter, which is not safe. It is safe to go the other way and narrow the parameter type e.g.
Action<object> oa = o => { Console.WriteLine(o.GetHashCode()); }
Action<string> sa = oa;
sa("test");
since any string
is also an object
.
Upvotes: 2
Reputation: 109762
Here's some annotated code that might also help you understand in detail.
It uses an Animal / Cat / Dog class heirarchy to illustrate why contravariance and covariance is the way it is for Action<T>
and Func<T>
.
using System;
namespace Demo
{
class Animal
{
public virtual void MakeNoise() {}
}
class Dog: Animal
{
public override void MakeNoise()
{
Bark();
}
public void Bark() {}
}
class Cat : Animal
{
public override void MakeNoise()
{
Meow();
}
public void Meow() {}
}
class Program
{
static void handleAnimal(Animal animal) // I can handle cats AND dogs.
{
animal.MakeNoise();
}
static void handleCat(Cat cat) // I only handle cats.
{
cat.Meow();
}
static Cat createCat() // I only create cats.
{
return new Cat();
}
static Dog createDog() // I only create dogs.
{
return new Dog();
}
static Animal createAnimal() // I only create animals.
{
return new Animal();
}
public static void Main()
{
// Action<T> is contravariant.
// Since the parameter of handleAnimal() is of type Animal,
// it can handle both cats and dogs. Therefore Action<Cat>
// and Action<Dog> can both be assigned from it.
Action<Cat> catAction = handleAnimal;
Action<Dog> dogAction = handleAnimal;
catAction(new Cat()); // Cat passed to handleAnimal() - OK.
dogAction(new Dog()); // Dog passed to handleAnimal() - OK.
// Imagine that Action<T> was covariant.
// Then you would be able to do this:
Action<Animal> animalAction = handleCat; // This line won't compile, because:
animalAction(new Animal()); // Animal passed to handleCat() - NOT OK!
// Func<T> has a covariant return type.
// Since the type returned from Func<Animal> is of type Animal,
// any type derived from Animal will do.
// Therefore it can be assigned from either createCat() or createDog().
Func<Animal> catFunc = createCat;
Func<Animal> dogFunc = createDog;
Func<Animal> animalFunc = createAnimal;
Animal animal1 = catFunc(); // Cat returned and assigned to Animal - OK.
Animal animal2 = dogFunc(); // Dog returned and assigned to Animal - OK.
Animal animal3 = animalFunc(); // Animal returned and assigned to Animal - OK.
// Imagine that Func<T> was contravariant.
// Then you would be able to do this:
Func<Cat> catMaker = createAnimal; // This line won't compile because:
Cat cat = catMaker(); // Animal would be assigned to Cat - NOT OK!
}
}
}
Upvotes: 1
Reputation: 152616
An Action<T>
takes a T
as an input and returns nothing, so it can be contravariant.
A Func<T>
takes no input and returns a T
, so it can be covariant.
They serve different purposes and are not interchangeable.
In general, an interface can be covariant when it only uses the generic parameters in outputs (e.g. methods that return a T
but do not take T
as an input, read-only properties).
The classic example is IEnumerable<T>
. it only returns T
s - it does not have any methods or properties that take a T
an an input.
An interface can be contravariant when it only uses the generic parameter(s) only as inputs
One example of this is IComparer<T>
. It takes two T
s and determines if they are equal to (or if one is "greater than" the other"). It has no return values that are based on T
.
when should I make my custom delegates Covariant and when make them contravariant ?
A delegate can be covariant if it only returns T
s. It can be contravariant if in only has inputs that are T
s.
Upvotes: 1
Reputation: 3492
The difference between Func and Action is simply whether you want the delegate to return a value (use Func) or not (use Action). Func is probably most commonly used in LINQ - for example in projections:
list.Select(x => x.SomeProperty)
or filtering:
list.Where(x => x.SomeValue == someOtherValue)
or key selection:
list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)
Action is more commonly used for things like List<T>.ForEach
: execute the given action for each item in the list. I use this less often than Func, although I do sometimes use the parameterless version for things like Control.BeginInvoke and Dispatcher.BeginInvoke.
Predicate is just a special cased Func really, introduced before all of the Func and most of the Action delegates came along. I suspect that if we'd already had Func and Action in their various guises, Predicate wouldn't have been introduced... although it does impart a certain meaning to the use of the delegate, whereas Func and Action are used for widely disparate purposes.
Predicate is mostly used in List<T>
for methods like FindAll and RemoveAll
Upvotes: 0