Register dependency with generic sub-type - For Mediatr

Viewed 26

I realise that I saw (very similar) questions being asked, but this seems SLIGHTLY different than the ones usually asked.

I got this notification / notification handler

public class GenericEvent<T> : INotification
{
    public T Value { get; set; }
}

public class GenericEventHandler<T> : INotificationHandler<GenericEvent<T>>
{
    public Task Handle(GenericEvent<T> notification, CancellationToken ct)
    {
        throw new NotImplementedException();
    }
}

Registering the dependency will give a the following C# compiler error:

CS7003: Unexpected use of an unbound generic name

public static class RegistrationTest
{
    public static void Register(IServiceCollection services)
    {
        services.AddTransient(
            typeof(INotificationHandler<GenericEvent<>>), // <!-- CS7003 here
            typeof(GenericEventHandler<>));
    }
}

Screenshot of the CS7003 compiler error in the IDE

I mean of course, the error generated makes sense. But there must be some other way to actually get this registered. FWIW, using the OOTB DI Container

1 Answers

Using C# you can either specify an open generic type (e.g. typeof(IEnumerable<>)) or a closed generic type (e.g. typeof(IEnumerable<List<int>>)) but not a partially-closed generic type (e.g. typeof(IEnumerable<List<>>)). This gives the CS7003 compile error.

Partially-closed generic types can only be created using reflection, for instance:

Type partiallyClosed = typeof(IEnumerable<>).MakeGenericType(typeof(List<>));

However, although you can create a partially-closed generic type at runtime, your DI Container, MS.DI, does not support this. Typically, this won't be a problem, because mature DI Containers are able to figure out your type constraints when making the following registration:

// WARNING: Does not work
services.AddTransient(typeof(INotificationHandler<>), typeof(GenericEventHandler<>));

Although this code snippet compiles, it doesn't work when applied on MS.DI. That's because MS.DI's generic type system is 'straight forward' (although I'd rather say 'naive'). It only supports the simplest forms of generic type mappings, as I expressed in this answer recently.

The only workaround (while sticking to using MS.DI) is to register every required closed implementation explicitly:

services.AddTransient<INotificationHandler<GenericEvent<Foo>>,
    GenericEventHandler<Foo>>();
services.AddTransient<INotificationHandler<GenericEvent<Bar>>,
    GenericEventHandler<Bar>>();
services.AddTransient<INotificationHandler<GenericEvent<Zoo>>,
    GenericEventHandler<Zoo>>();
services.AddTransient<INotificationHandler<GenericEvent<Rar>>,
    GenericEventHandler<Rar>>();

Another option is to move to a more mature and feature rich DI Container.

Related