6

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?

Steven
  • 166,672
  • 24
  • 332
  • 435
Kai
  • 871
  • 2
  • 18
  • 39

2 Answers2

5

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);
            }
        }
    };
}
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 2
    Hi Steven, thank you a lot. This actually works and solves my issue although I'd imagined there would be a way archiving this without creating separate implementations. – Kai Jun 27 '14 at 14:54
  • @ka: but do note that I do agree with Qujck about the separation in your design. I think your design improves with his suggestion. – Steven Jun 28 '14 at 13:16
  • Agreed. Both answers are correct and highly appreciated. Anyway, the initial question has been best answered by you. – Kai Jun 28 '14 at 14:22
3

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);
qujck
  • 14,388
  • 4
  • 45
  • 74
  • 1
    Hi qujck. Your approach is much like Stevens, although it's a bit cleaner through the separation of the concerns. – Kai Jun 27 '14 at 16:40