Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SimpleInjector HowTo Register multiple Open Generic Interfaces for a Single Generic Implementation

I'm trying to get started with SimpleInjector as an IOC Container and up to now I'm pretty happy with it. But right now I'm stuck on a problem I can't solve. I searched on SO and in the documentation, but it seems to be not answered yet. I've seen the howto doc from SimpleInjector but that doesn't cover open generic interfaces.

I have two generic interfaces like these:

public interface IEventPublisher<TEvent>
{
   void Publish(TEvent Event);
}
public interface IEventSubscriber<TEvent>
{
    void Subscribe(Action<TEvent> CallBack);
}

And one open generic implementation for those two:

class EventMediator<T> : IEventPublisher<T>, IEventSubscriber<T>
{
    List<Action<T>> Subscriptions = new List<Action<T>>();

    public void Publish(T Event)
    {
        foreach (var Subscription in this.Subscriptions)
            Subscription.Invoke(Event);
    }

    public void Subscribe(Action<T> CallBack)
    {
        this.Subscriptions.Add(CallBack);
    }
}

In my Application I'm setting up SimpleInjector like this:

this.Container = new SimpleInjector.Container();
this.Container.RegisterOpenGeneric(typeof(IEventPublisher<>), typeof(EventMediator<>), Lifestyle.Singleton);
this.Container.RegisterOpenGeneric(typeof(IEventSubscriber<>), typeof(EventMediator<>), Lifestyle.Singleton);
this.Container.Verify();

What I'm trying to archive is: I'd like to get exactly the same instance when asking for a IEventPublisher or an IEventSubscriber. And furthermore this Instance shall be a singleton for any T.

I've tested this with these lines:

class DummyEvent {}

var p = this.Container.GetInstance<IEventPublisher<DummyEvent>>();
var s = this.Container.GetInstance<IEventSubscriber<DummyEvent>>();
var areSame = (object.ReferenceEquals(p,s));

Unfortunatly p and s don't refer to the same instance. Anyone happens to know a solution to this problem?

like image 384
Kai Avatar asked Jun 27 '14 12:06

Kai


2 Answers

There are certain solutions for this, here's one: Create separate implementations for IEventPublisher<T> and IEventSubscriber<T> and let them delegate to the EventMediator<T>. For instance with these implementations:

public class EventPublisher<TEvent> : IEventPublisher<TEvent>
{
    private readonly EventMediator<TEvent> mediator;
    public EventPublisher(EventMediator<TEvent> mediator) {
        this.mediator = mediator;
    }

    public void Publish(TEvent Event) {
        this.mediator.Publish(Event);
    }
}

public class EventSubscriber<TEvent> : IEventSubscriber<TEvent>
{
    private readonly EventMediator<TEvent> mediator;
    public EventSubscriber(EventMediator<TEvent> mediator) {
        this.mediator = mediator;
    }

    public void Subscribe(Action<TEvent> CallBack) {
        this.mediator.Subscribe(Callback);
    }
}

Now you make the registrations as follows:

container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));
container.RegisterSingleOpenGeneric(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleOpenGeneric(typeof(IEventSubscriber<>), typeof(EventSubscriber<>));

Now both the EventPublisher<DummyEvent> and EventSubscriber<DummyEvent> will point at the same EventMediator<DummyEvent> instance.

Another way to achieve this without the extra type is to make use of the ResolveUnregisteredType event (which is what the RegisterOpenGeneric extension method itself uses under the covers). Your configuration would look like this:

container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));

container.ResolveUnregisteredType += (s, e) =>
{
    if (e.UnregisteredServiceType.IsGenericType)
    {
        var def = e.UnregisteredServiceType.GetGenericTypeDefinition();

        if (def == typeof(IEventPublisher<>) || def == typeof(IEventSubscriber<>))
        {
            var mediatorType = typeof(EventMediator<>)
                .MakeGenericType(e.UnregisteredServiceType.GetGenericArguments()[0]);
            var producer = container.GetRegistration(mediatorType, true);
            e.Register(producer.Registration);
        }
    }
};

You could even extract this code into a more general extension method. This way your registration would look like this:

container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));
container.ForwardOpenGenericTo(typeof(IEventPublisher<>), typeof(EventMediator<>));
container.ForwardOpenGenericTo(typeof(IEventSubscriber<>), typeof(EventMediator<>));

The extension method would look like this:

public static void ForwardOpenGenericTo(this Container container,
    Type openGenericServiceType, Type openGenericServiceTypeToForwardTo)
{
    container.ResolveUnregisteredType += (s, e) =>
    {
        var type = e.UnregisteredServiceType;
        if (type.IsGenericType)
        {
            if (type.GetGenericTypeDefinition() == openGenericServiceType)
            {
                var forwardToType = openGenericServiceTypeToForwardTo.MakeGenericType(
                    type.GetGenericArguments());
                var producer = container.GetRegistration(forwardToType, true);
                e.Register(producer.Registration);
            }
        }
    };
}
like image 182
Steven Avatar answered Nov 07 '22 19:11

Steven


You are registering IEventPublisher and IEventSubscriber as separate singletons. You will need to refactor your code in one way or another. One solution is to separate the 3 responsibilities of your mediator:

The Subscriber

public interface IEventSubscriber<TEvent>
{
    void Subscribe(Action<TEvent> CallBack);
}

public class EventSubscriber<T> : IEventSubscriber<T>
{
    public readonly ISubscriptions<T> subscriptions;

    public EventSubscriber(ISubscriptions<T> subscriptions)
    {
        this.subscriptions = subscriptions;
    }

    public void Subscribe(Action<T> CallBack)
    {
        this.subscriptions.Add(CallBack);
    }
}

The Publisher

public interface IEventPublisher<TEvent>
{
    void Publish(TEvent Event);
}

public class EventPublisher<T> : IEventPublisher<T>
{
    public readonly ISubscriptions<T> subscriptions;

    public EventPublisher(ISubscriptions<T> subscriptions)
    {
        this.subscriptions = subscriptions;
    }

    public void Publish(T Event)
    {

        foreach (var subscription in this.subscriptions)
        {
            subscription.Invoke(Event);
        }
    }
}

The Subscriptions

public interface ISubscriptions<T> : IList<Action<T>> { }

public class Subscriptions<T> : List<Action<T>>, ISubscriptions<T> { }

Only the subscriptions need to be registered as singleton

var container = new Container();
container.RegisterOpenGeneric(typeof(IEventSubscriber<>), typeof(EventSubscriber<>));
container.RegisterOpenGeneric(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleOpenGeneric(typeof(ISubscriptions<>), typeof(Subscriptions<>));
container.Verify();

var p = container.GetInstance<IEventPublisher<DummyEvent>>();
var s = container.GetInstance<IEventSubscriber<DummyEvent>>();
Assert.That(
    (p as EventPublisher<DummyEvent>).subscriptions  == 
    (s as EventSubscriber<DummyEvent>).subscriptions);
like image 35
qujck Avatar answered Nov 07 '22 17:11

qujck