Covariance in delegate, any example?

I'm reading this msdn article, the contravariance example (keyboard and mouse event) is great and useful, while the covariance example (Mammal and Dog) doesn't look so.

The keyboard and mouse event is great since you can use 1 handler for multiple cases; but I can't think of the advantages of assigning a handler that returns a more derived type to a handler returning base type, not to mention it looks like less common to have a delegate which cares about return type?

Could someone please provide a more practical example of covariance in delegate?


Here's a "real world" example where I implement a service locator. I want to make a class where I can register "factory" delegates that know how to produce instances of some type, then resolve instances of some type later on. Here is what that looks like:

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

Now, where I'm doing container.Register<ISomeService>(() => new SomeService()) , the covariance of Func delegates comes into play on two levels:

  • I can pass in a Func<SomeService> and it is assignable where a Func<ISomeService> is expected with no problems, because a SomeService is a ISomeService
  • Inside the Register method, a Func<T> can be assigned where a Func<object> is expected with no problems, because any reference type is an object
  • If a Func<SomeService> wasn't assignable to a Func<ISomeService> , or Func<ISomeService> wasn't assignable to a Func<object> (via covariance), this example wouldn't work.


    You are right, it is not common an event to return with a value, it's kinda convention (actually it is more than convention: see extra reading #2). However delegates are not only for events, basically they are the .NET version of the near half century old C style "pointer to a function" (C terminology).

    To leave the mystery left on events and delegates and listeners (java) in control flow concept they are just simple callbacks, so it is completely reasonable if they have return value.

    So from this callback point of view: Say I would like to process animals. I would like to use a function pointer (I mean: delegate or lambda) to do a part of this processing. Lets call it FeedAnimal. I would like to have an other skeleton method which calls this feed method, lets call it CareAnimal. I would like to plugin the feed algorithm to the care algorithm with run time variable mode, so the Care will have a delegate parameter: feed. After feeding the feed method returns an animal.

    Now the point: The feed will have different implementations for Dog and Cat, one returns with Dog and the other returns with Cat.... and the Care() method accepts a delegate parameter which returns with Animal.

    [Extra reading #1]: This kind of polymorphic implementation is the not OOP polymorphism implementaion. In OOP you can achieve similar with virtual method overloads.

    [Extra reading #2]: The really disturbing thing about delegates (and events) that they are multicast delegates I mean one single delegate (which is a multicast delegate by default) can contain many method entry point. When it is invoked then all contained methods invoked in a cycle in not specified order. However there will be one return value in case the signature is not void. Of course this is confusing, so we safely say: If we use the multicast feature of delegates (or events) then it makes no sense other signature than void return. Events are typically multicast, this comes from the Publisher/Subscriber DP metaphore: Many subscriber (handler) can subscribe (+=) to the publishers publication without knowing anything about each other.


    Well, if you look at the declaration of the Func<T, TResult> Delegate.

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

    You can see that the type of the input parameter is contravariant but the type of the result or return value is covariant.

    The familiar Linq extension Select , has an overload that accepts this delegate.

    Additionally, note that the return type of Select is IEnumerable<T> , that is a covariant interface, ie

    public IEnumerable<out T>
    {
         ...
    }
    

    Now consider the types,

    abstract class Mammal
    {
    }
    

    and

    class Dog : Mammal
    {
    }
    

    I can declare an instance of the delegate.

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

    I can pass this delegate to Select without variance.

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

    Now, because the input of the function is contravariant, I can do

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

    Now here's the point , because the result is covariant, I can do

    IEnumerable<Dogs> dogs = new List<Dog>().Select<Dog, Dog>(doItToMammals);
    
    链接地址: http://www.djcxy.com/p/87356.html

    上一篇: SonTQube测试覆盖与MsTest

    下一篇: 代表中的协变性,任何示例?