代表中的协方差,任何例子?

时间:2015-08-07 08:49:46

标签: c# .net delegates covariance contravariance

我正在阅读this msdn article,逆向示例(键盘和鼠标事件)非常有用,而协方差示例(哺乳动物和狗)看起来并不那样。

键盘和鼠标事件很棒,因为你可以为多个案例使用1个处理程序;但是我想不出将处理器返回更多派生类型的处理程序返回给基类型的处理程序的优点,更不用说让代理关注返回类型看起来不那么常见了?

有人可以在代表中提供一个更实际的协方差例子吗?

3 个答案:

答案 0 :(得分:3)

这是我实现服务定位器的“真实世界”示例。我想创建一个类,我可以注册“工厂”委托,知道如何生成某种类型的实例,然后解决某种类型的实例。这是看起来像:

interface ISomeService { ... }
class SomeService : ISomeService { ... }

class IocContainer
{
    private readonly Dictionary<Type, Func<object>> _factories = new Dictionary<Type, Func<object>>();

    public void Register<T>(Func<T> factory)
        where T: class 
    {
        _factories[typeof(T)] = factory;
    }

    // Note: this is C#6, refactor if not using VS2015
    public T Resolve<T>() => (T)_factories[typeof(T)]();
}

class Program
{
    static void Main(string[] args)
    {
        var container = new IocContainer();
        container.Register<ISomeService>(() => new SomeService());

        // ...

        var service = container.Resolve<ISomeService>();
    }
}

现在,我正在做container.Register<ISomeService>(() => new SomeService())Func代表的协方差在两个层面上发挥作用:

  1. 我可以传递一个Func<SomeService>并且可以指定Func<ISomeService>没有问题,因为SomeServiceISomeService
  2. Register方法中,Func<T>可以分配到预期Func<object>没有问题的位置,因为任何引用类型都是object
  3. 如果Func<SomeService>无法分配给Func<ISomeService>,或者Func<ISomeService>无法分配给Func<object>(通过协方差),则此示例不起作用

答案 1 :(得分:2)

你是对的,用一个值返回的事件并不常见,它是一种惯例(实际上它不仅仅是约定:看额外阅读#2)。然而,代表不仅仅是为了事件,基本上它们是近半个世纪的旧C风格“指向函数的指针”的.NET版本(C术语)。

在控制流概念中留下关于事件和委托人和听众(java)的谜团,它们只是简单的回调,所以如果它们具有返回值则完全合理。

从这个回调的角度来看: 说我想处理动物。我想使用函数指针(我的意思是:委托或lambda)来执行此处理的部分。让我们称之为FeedAnimal。我想有一个其他骨架方法调用这个feed方法,让我们称之为CareAnimal。我想插入使用运行时变量模式的关注算法的feed算法,因此Care将有一个委托参数:feed。喂食后饲料方法会返回一只动物。

现在重点:Feed将有不同的Dog和Cat实现,一个返回Dog,另一个返回Cat ....而Care()方法接受一个返回Animal的委托参数。

[额外阅读#1]:这种多态实现是 not OOP多态实现。在OOP中,您可以实现与虚方法重载类似的功能。

[额外阅读#2]:关于代表(和事件)的真的令人不安的事情,他们是多播委托我指的是一个单独的委托(这是一个多播委托)默认情况下)可以包含许多方法入口点。调用它时,所有包含的方法都在 指定 order 的循环中调用。但是,如果签名无效,则会有一个返回值。当然这很令人困惑,所以我们安全地说:如果我们使用委托(或事件)的多播功能,那么除了void返回之外没有任何意义。 事件通常是多播的,这来自发布者/订阅者DP的比喻:许多订阅者(处理者)可以订阅(+ =)发布者出版物,而不知道彼此之间的任何事情。

答案 2 :(得分:0)

好吧,如果你看一下Func<T, TResult> Delegate.

的声明
public delegate TResult Func<in T, out TResult>(
    T arg
)

您可以看到输入参数的类型是逆变量,但结果或返回值的类型是协变的。

熟悉的Linq扩展程序Selectan overload that accepts this delegate

此外,请注意Select的返回类型为IEnumerable<T>, 这是一个协变界面,即

public IEnumerable<out T>
{
    \\ ...
}

现在考虑类型,

abstract class Mammal
{
}

class Dog : Mammal
{
}

我可以声明委托的实例。

var doItToMammals = new Func<Mammal, Mammal>(mammal => mammal);

我可以将此委托传递给Select而不会有任何差异。

IEnumerable<Mammal> mammals = new List<Mammal>().Select(doItToMammals);

现在,因为函数的输入是逆变的,我可以做

IEnumerable<Mammal> mammals = new List<Dog>().Select(doItToMammals);

现在点,因为结果是协变的,我可以做到

IEnumerable<Dogs> dogs = new List<Dog>().Select<Dog, Dog>(doItToMammals);