Search code examples
c#genericssimple-injectoropen-generics

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?


Solution

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