代表差异

时间:2016-05-31 14:40:59

标签: c# delegates variance

我很难理解为什么Action<T>是逆变和Func<T>协变,为什么Action<T>应该是逆变的,为什么Func<T>应该是协变的,任何准则关于何时使用一个以及何时使用另一个。

4 个答案:

答案 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 等方法