Pick circuit and contravariance with a simple injector

This question comes from my trying to create a Simple Injector implementation for MediatR: https://github.com/jbogard/MediatR/pull/14 .

I am having problems when trying to resolve the implementation of a common handler interface. Consider the following notification handler interface:

public interface INotificationHandler<in TNotification>
    where TNotification : INotification
{
    void Handle(TNotification notification);
}

      

INotifcation

is just an empty marker interface.

I have defined the following handlers for the event Pinged

(which implements INotification

):

public class PingedHandler : INotificationHandler<Pinged>
{
    public void Handle(Pinged notification) { }
}

public class PingedHandler2 : INotificationHandler<Pinged>
{
    public void Handle(Pinged notification) { }
}

      

And also a generic handler (note that everyone should handle this INotification

):

public class GenericHandler : INotificationHandler<INotification>
{
    public void Handle(INotification notification) { }
}

      

With the following registration:

var container = new Container();

container.RegisterManyForOpenGeneric(
    typeof (INotificationHandler<>),
    (service, impls) => container.RegisterAll(service, impls),
    AppDomain.CurrentDomain.GetAssemblies());

      

Now I expect:

GetAllInstances<INotificationHandler<Pinged>>();

      

for both permission PingedHandler

and PingedHandler2

which it performs. But it does not resolve GenericHandler

as it implements INotificationHandler<INotification>

, not INotificationHandler<Pinged>

. I wonder if there is a way to let Simple Injector search the entire object graph and allow whatever is there Pinged

too.

I found a blog post from Stephen about overlap and inconsistency, but I can't seem to get it to work my example.

+3


source to share


2 answers


tl; dr: This is a bug / flaw in Simple Injector v2.6.0, which is fixed in version 2.2.0. The configuration in the question (and shown below) now works.


To summarize @qujck's answer and @Steven's comment: I managed to get it to work by installing Simple Injector v2.7.0-beta2 with the following configuration (actually the same as in the question):



// Simple Injector v3.x
container.RegisterCollection(typeof(INotificationHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(
    typeof(INotificationHandler<>),
    container.RegisterAll,
    AppDomain.CurrentDomain.GetAssemblies());

      

Now Simple Injector can resolve PingedHandler

, PingedHandler2

and GenericHandler

upon request:

container.GetAllInstances<INotificationHandler<Pinged>>();

      

+2


source


UPDATE

As of Simple Injector 2.7, this feature is now standard and you no longer need the workaround shown below.


You are missing the option MultipleDispatchEventHandler

described in this article. Taking the underlying logic and applying it to your abstractions gives the result you expect:

[Fact]
public void RegisterAll_Contravariant_Succeeds()
{
    var container = new Container();

    container.RegisterManyForOpenGeneric(
        typeof(INotificationHandler<>),
        (service, impls) => container.RegisterAll(service, impls),
        AppDomain.CurrentDomain.GetAssemblies());

    var handlersType = typeof(IEnumerable<INotificationHandler<Pinged>>);

    var handlersCollection = (
        from r in container.GetCurrentRegistrations()
        where handlersType.IsAssignableFrom(r.ServiceType)
        select r.GetInstance())
        .Cast<IEnumerable<INotificationHandler<Pinged>>>()
        .ToArray();

    var result = 
        from handlers in handlersCollection
        from handler in handlers
        select handler;

    Assert.Equal(3, result.Count());
}

      



But it might be easier to do GenericHandler

generic:

public class GenericHandler<TNotification> : INotificationHandler<TNotification>
    where TNotification : INotification
{
    public void Handle(TNotification notification) { }
}

      

Providing easier registration

[Fact]
public void RegisterAll_WithOpenAndClosedGenerics_Succeeds()
{
    var container = new Container();

    var types = OpenGenericBatchRegistrationExtensions
        .GetTypesToRegister(
            container,
            typeof(INotificationHandler<>),
            AccessibilityOption.AllTypes,
            AppDomain.CurrentDomain.GetAssemblies())
        .ToList();

    types.Add(typeof(GenericHandler<>));

    container.RegisterAll(typeof(INotificationHandler<>), types);

    var result = container.GetAllInstances<INotificationHandler<Pinged>>().ToList();

    Assert.Equal(3, result.Count());
}

      

+3


source







All Articles