代表方差

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 Ts - 它没有任何采用 T 输入的方法或属性.

一个接口可以是逆变当它只使用通用参数作为输入

其中一个例子是 IComparer<T>。它需要两个 T 并确定它们是否等于(或者如果一个等于 "greater than" 另一个”)。它没有基于 T 的 return 值。

when should I make my custom delegates Covariant and when make them contravariant ?

一个委托如果只有 returnsTs 就可以是协变的。如果 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> 中主要用于 FindAllRemoveAll

等方法

这里有一些带注释的代码,可能也有助于您详细理解。

它使用 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!
        }
    }
}