【问题标题】:Covariance in delegate, any example?委托中的协方差,有什么例子吗?
【发布时间】:2015-08-07 08:49:46
【问题描述】:

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

键盘和鼠标事件非常棒,因为您可以为多种情况使用 1 个处理程序;但我想不出将返回更多派生类型的处理程序分配给返回基类型的处理程序的好处,更不用说拥有关心返回类型的委托看起来不太常见?

有人可以提供一个更实际的委托协方差示例吗?

【问题讨论】:

    标签: c# .net delegates covariance contravariance


    【解决方案1】:

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

    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&lt;ISomeService&gt;(() =&gt; new SomeService()) 做的地方,Func 代表的协方差在两个层面上发挥作用:

    1. 我可以传入Func&lt;SomeService&gt;,它可以分配到Func&lt;ISomeService&gt; 没有问题的地方,因为SomeServiceISomeService
    2. Register 方法中,可以指定Func&lt;T&gt;,因为任何引用类型都是object

    如果Func&lt;SomeService&gt; 不能分配给Func&lt;ISomeService&gt;,或者Func&lt;ISomeService&gt; 不能分配给Func&lt;object&gt;(通过协方差),则此示例将不起作用。

    【讨论】:

    • 逆变也可以吗?例如。每种类型的动作?我无法让它工作。
    【解决方案2】:

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

    为了让控制流概念中的事件、委托和侦听器(java)留下神秘感,它们只是简单的回调,所以如果它们有返回值是完全合理的。

    所以从这个回调的角度来看: 假设我想处理动物。我想使用函数指针(我的意思是:委托或 lambda)来执行此处理的 part。让我们称之为 FeedAnimal。我想要一个调用这个提要方法的其他骨架方法,我们称之为 CareAnimal。我想 plugin 将 feed 算法添加到 care 算法 with 运行时变量模式中,所以 Care 会有一个委托参数:feed。喂完 feed 方法后返回一个动物。

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

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

    [额外阅读 #2]:真正令人不安的关于代表(和事件)的事情是,他们是 multicast 代表我的意思是一个单一的代表(这是一个多播代表默认情况下)可以包含许多方法入口点。当它被调用时,所有包含的方法都以not 指定 顺序的循环调用。但是,如果签名不是无效的,则会有 one 返回值。当然这是令人困惑的,所以我们有把握地说:如果我们使用委托(或事件)的多播特性,那么除了 void return 之外的其他签名没有任何意义。 事件通常是多播的,这来自发布者/订阅者 DP 隐喻:许多订阅者(处理程序)可以订阅 (+=) 发布者发布彼此不知道任何信息。

    【讨论】:

    • 我不明白这与事件有什么关系
    • @Asad 他们没有,但询问者似乎认为他们有。
    • @Asad:如果事件(什么是更高级别的隐喻)不与委托混合,并且更普遍地与回调控制流混合,那就太好了,但不幸的是它们混合了。此外,引用的文章(通常 MSDN 总是只讨论事件(不好)。
    • @g.pickardou 感谢您的回复。但我想到了,Feed 方法看起来更像是有一个void Feed(Animal a) 的签名而不是Animal Feed(),因为动物是“被喂养”的,无论如何?所以 Feed 示例对我来说仍然更像是一个逆变示例……我认为 Factory 示例(由 Asad 编写)更实用,尽管它只是一个典型示例。
    • @g.pickardou 顺便说一句,额外的点 2 是一个很棒的观点。所以我会说协方差目前只对回调有用(而不是多播)。
    【解决方案3】:

    好吧,如果你看看Func&lt;T, TResult&gt; Delegate.的声明

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

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

    熟悉的 Linq 扩展 Select,有 an overload that accepts this delegate

    另外注意Select的返回类型是IEnumerable&lt;T&gt;, 那是一个协变接口,即

    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);
    

    【讨论】:

      猜你喜欢
      • 2016-09-24
      • 1970-01-01
      • 2010-12-13
      • 2012-05-06
      • 1970-01-01
      • 2011-10-30
      • 2017-12-09
      • 2013-06-18
      • 2012-02-08
      相关资源
      最近更新 更多