我很难理解为什么Action<T>
是逆变和Func<T>
协变,为什么Action<T>
应该是逆变的,为什么Func<T>
应该是协变的,任何准则关于何时使用一个以及何时使用另一个。
答案 0 :(得分:2)
如果Action<T>
是协变的,您可以这样做:
Action<string> sa = s => { Console.WriteLine(s[0]); }
Action<object> oa = sa;
oa(1);
并将int
传递给带有字符串参数的操作,这是不安全的。走另一条路并缩小参数类型是安全的,例如
Action<object> oa = o => { Console.WriteLine(o.GetHashCode()); }
Action<string> sa = oa;
sa("test");
因为任何string
也是object
。
答案 1 :(得分:1)
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 50));
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 50));
将Action<T>
作为输入并且不返回任何内容,因此它可以是逆变的。
T
不接受任何输入并返回Func<T>
,因此它可以是协变的。
它们用于不同的目的,不可互换。
通常,当接口只使用 outputs 中的泛型参数时,接口可以是协变(例如,返回T
但不接受{的方法{1}}作为输入,只读属性)。
经典的例子是T
。只有返回 T
s - 它没有任何方法或属性可以使用IEnumerable<T>
和输入。
当接口仅使用通用参数作为输入 时,接口可以是逆变
其中一个例子是IComparer<T>
。它需要两个T
并确定它们是否相等(或者如果一个“大于”另一个“)。它没有基于T
的返回值。
什么时候应该让我的自定义代表Covariant以及什么时候让它们逆变?
如果只有返回 T
s,则委托可以是协变的。如果只有{em>输入是T
s,它可以是逆变的。
答案 2 :(得分:1)
这里有一些带注释的代码,可能也会帮助您详细了解。
它使用Animal / Cat / Dog类heirarchy来说明为什么逆变和协方差是Action<T>
和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!
}
}
}
答案 3 :(得分:0)
Func和Action之间的区别仅在于您是否希望委托返回值(使用Func)或不使用(使用Action)。 Func可能是LINQ中最常用的 - 例如在投影中:
list.Select(x => x.SomeProperty)
或过滤:
list.Where(x => x.SomeValue == someOtherValue)
或密钥选择:
list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)
动作更常用于List<T>.ForEach
之类的事情:对列表中的每个项目执行给定的操作。我使用它比Func更少,尽管有时我会使用无参数版本来控制Control.BeginInvoke和Dispatcher.BeginInvoke。
Predicate只是一个特殊的套装Func,在所有Func和大多数Action代表出现之前就已经介绍过了。我怀疑,如果我们已经以各种形式使用了Func和Action,那么Predicate将不会被引入......尽管它确实赋予了代表使用某种意义,而Func和Action则是用于广泛不同的目的。
谓词主要用于List<T>
,用于 FindAll 和 RemoveAll 等方法