代表方差
Delegates variance
我很难理解为什么 Action<T>
是逆变的而 Func<T>
是协变的,为什么 Action<T>
应该是逆变的,为什么 Func<T>
应该是协变的,关于何时使用一个以及何时使用另一个的任何指导方针。
一个Action<T>
接受一个T
作为输入而return什么都没有,所以它可以是逆变的。
A Func<T>
没有输入并且 returnS 是 T
,因此它可以是协变的。
它们有不同的用途,不可互换。
一般来说,当接口仅使用 outputs 中的通用参数时(例如 return a T
但不要将 T
作为输入,只读属性)。
经典的例子是IEnumerable<T>
。它只有 returns T
s - 它没有任何采用 T
和 输入的方法或属性.
一个接口可以是逆变当它只使用通用参数作为输入
其中一个例子是 IComparer<T>
。它需要两个 T
并确定它们是否等于(或者如果一个等于 "greater than" 另一个”)。它没有基于 T
的 return 值。
when should I make my custom delegates Covariant and when make them contravariant ?
一个委托如果只有 returnsT
s 就可以是协变的。如果 in 只有 输入 是 T
,它可以是逆变的。
如果 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
。
Func 和 Action 之间的区别只是您是否希望委托 return 一个值(使用 Func)或不(使用 Action)。
Func 可能在 LINQ 中最常用 - 例如在投影中:
list.Select(x => x.SomeProperty)
或筛选:
list.Where(x => x.SomeValue == someOtherValue)
或键选择:
list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)
Action 更常用于 List<T>.ForEach
之类的事情:对列表中的每个项目执行给定的操作。我使用它的频率低于 Func,虽然我有时会使用无参数版本来处理 Control.BeginInvoke 和 Dispatcher.BeginInvoke.
之类的东西
Predicate 实际上只是一个特殊的 Func,在所有 Func 和大多数 Action 委托出现之前引入。我怀疑如果我们已经有了各种形式的 Func 和 Action,就不会引入 Predicate 了……尽管它确实赋予委托的使用一定的意义,而 Func 和 Action 用于广泛不同的用途目的。
Predicate 在 List<T>
中主要用于 FindAll 和 RemoveAll
等方法
这里有一些带注释的代码,可能也有助于您详细理解。
它使用 Animal / Cat / Dog class 层次结构来说明为什么逆变和协变是 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!
}
}
}
我很难理解为什么 Action<T>
是逆变的而 Func<T>
是协变的,为什么 Action<T>
应该是逆变的,为什么 Func<T>
应该是协变的,关于何时使用一个以及何时使用另一个的任何指导方针。
一个Action<T>
接受一个T
作为输入而return什么都没有,所以它可以是逆变的。
A Func<T>
没有输入并且 returnS 是 T
,因此它可以是协变的。
它们有不同的用途,不可互换。
一般来说,当接口仅使用 outputs 中的通用参数时(例如 return a T
但不要将 T
作为输入,只读属性)。
经典的例子是IEnumerable<T>
。它只有 returns T
s - 它没有任何采用 T
和 输入的方法或属性.
一个接口可以是逆变当它只使用通用参数作为输入
其中一个例子是 IComparer<T>
。它需要两个 T
并确定它们是否等于(或者如果一个等于 "greater than" 另一个”)。它没有基于 T
的 return 值。
when should I make my custom delegates Covariant and when make them contravariant ?
一个委托如果只有 returnsT
s 就可以是协变的。如果 in 只有 输入 是 T
,它可以是逆变的。
如果 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
。
Func 和 Action 之间的区别只是您是否希望委托 return 一个值(使用 Func)或不(使用 Action)。 Func 可能在 LINQ 中最常用 - 例如在投影中:
list.Select(x => x.SomeProperty)
或筛选:
list.Where(x => x.SomeValue == someOtherValue)
或键选择:
list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)
Action 更常用于 List<T>.ForEach
之类的事情:对列表中的每个项目执行给定的操作。我使用它的频率低于 Func,虽然我有时会使用无参数版本来处理 Control.BeginInvoke 和 Dispatcher.BeginInvoke.
Predicate 实际上只是一个特殊的 Func,在所有 Func 和大多数 Action 委托出现之前引入。我怀疑如果我们已经有了各种形式的 Func 和 Action,就不会引入 Predicate 了……尽管它确实赋予委托的使用一定的意义,而 Func 和 Action 用于广泛不同的用途目的。
Predicate 在 List<T>
中主要用于 FindAll 和 RemoveAll
这里有一些带注释的代码,可能也有助于您详细理解。
它使用 Animal / Cat / Dog class 层次结构来说明为什么逆变和协变是 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!
}
}
}