Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 964
Marson Mao Avatar asked Aug 07 '15 08:08

Marson Mao


2 Answers

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:

  1. 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
  2. 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.

like image 158
Asad Saeeduddin Avatar answered Sep 29 '22 00:09

Asad Saeeduddin


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.

like image 41
g.pickardou Avatar answered Sep 29 '22 01:09

g.pickardou