I'm unable to figure out the correct way to use RegisterAllOpenGeneric

I have these simple definitions:

public interface ISubscribeTo<T> { }
public class AnEventOf<T> { }

public interface IMarker { }
public class PocoB : IMarker { }

and a normal Subscriber:

public class SubscribeToPocoB : ISubscribeTo<AnEventOf<PocoB>>
{
}

which is registered with this code:

private void RegisterSubscribers(Container container)
{
    var implementations = new List<Type>();

    container.RegisterManyForOpenGeneric(typeof(ISubscribeTo<>),
        AccessibilityOption.PublicTypesOnly,
        (serviceType, implTypes) =>
        {
            container.RegisterAll(serviceType, implTypes);

            implementations.AddRange(implTypes);
        },
        AppDomain.CurrentDomain.GetAssemblies()
    );

    implementations
        .Distinct()
        .ToList()
        .ForEach(type => container.Register(type));

    container.Verify();
}

and is returned when calling:

var registrations = container
            .GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();

All good so far.

PocoB also implements the interface IMarker and I am trying to create a Subscriber to AnEventOf<IMarker>> that will also be returned when calling GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();.

I have tried 3 different definitions:

public class SubscribeToIMarker1<TMarker> : ISubscribeTo<AnEventOf<TMarker>>
    where TMarker : IMarker
{
}

public class SubscribeToIMarker2<TAnEventOf> : ISubscribeTo<TAnEventOf>
    where TAnEventOf : AnEventOf<IMarker>
{
}

public class SubscribeToIMarker3<TMarker> : ISubscribeTo<TMarker>
    where TMarker : IMarker
{
}

Here are the different tests methods I have written - none of the tests work, they all only return SubscribeToPocoB:

[Test]
public void GetAllInstances_PocoB1_ReturnTwoRegistrations()
{
    Container container = new Container();

    container.RegisterAllOpenGeneric(
        typeof(ISubscribeTo<>),
        typeof(SubscribeToIMarker1<>));

    RegisterSubscribers(container);

    var registrations = container
        .GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();

    Assert
        .That(registrations.Count(),
            Is.EqualTo(2));
}

[Test]
public void GetAllInstances_PocoB2_ReturnTwoRegistrations()
{
    Container container = new Container();

    container.RegisterAllOpenGeneric(
        typeof(ISubscribeTo<>),
        typeof(SubscribeToIMarker2<>));

    RegisterSubscribers(container);

    var registrations = container
        .GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();

    Assert
        .That(registrations.Count(),
            Is.EqualTo(2));
}

[Test]
public void GetAllInstances_PocoB3_ReturnTwoRegistrations()
{
    Container container = new Container();

    container.RegisterAllOpenGeneric(
        typeof(ISubscribeTo<>),
        typeof(SubscribeToIMarker3<>));

    RegisterSubscribers(container);

    var registrations = container
        .GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();

    Assert
        .That(registrations.Count(),
            Is.EqualTo(2));
}

Do I need to do more than this to correctly register all open generics (e.g. SubscribeToIMarker1)?

container.RegisterAllOpenGeneric(
    typeof(ISubscribeTo<>),
    typeof(SubscribeToIMarker1<>));
有帮助吗?

解决方案

The RegisterAllOpenGeneric is the collection equivalent of the RegisterOpenGeneric method. This means that you can register a set of open generic types as a collection. Just as with the RegisterOpenGeneric method however, the collection registered with RegisterAllOpenGeneric will only be resolved when there is no explicit registration.

In your case you make an explicit registration of a collection by calling container.RegisterAll(Type, Type[]). Since you registered this type explicitly, the registered collection of open generic types won't be built.

This is fine, but since your explicit collection registrations do not contain any open generic types, the resolved collection will only contain those registered non-generic types. Registering a set of open generic types using RegisterAllOpenGeneric does not automatically include them in any RegisterAll registrations.

So the solution is to register the open generic types as well. You can't however add the open generic types to the RegisterAll method, since this method only accepts closed generic types and non-generic types. The trick is to use the RegisterManyForOpenGeneric overload that accepts both the callback delegate and a collection of Types. This overload accepts open generic types and will distribute the closed generic versions back to the callback delegate.

This is what your RegisterSubscribers method should look like:

private void RegisterSubscribers(Container container)
{
    Type[] openGenericSubscribers = new[]
    { 
        typeof(SubscribeToIMarker1<>),
        typeof(SubscribeToIMarker2<>),
        typeof(SubscribeToIMarker3<>)
    };

    var nonGenericSubscribers = OpenGenericBatchRegistrationExtensions
        .GetTypesToRegister(container,
            typeof(ISubscribeTo<>),
            AccessibilityOption.PublicTypesOnly,
            AppDomain.CurrentDomain.GetAssemblies());

    container.RegisterManyForOpenGeneric(typeof(ISubscribeTo<>),
        container.RegisterAll,
        nonGenericSubscribers.Concat(openGenericSubscribers));

    container.RegisterAllOpenGeneric(typeof(ISubscribeTo<>), 
        openGenericSubscribers);

    container.Verify();
}

What's happening here is that instead of calling the RegisterManyForOpenGeneric that accepts a collection of Assembly objects, we call the OpenGenericBatchRegistrationExtensions.GetTypesToRegister method to get the list of non-generic subscribers. The RegisterManyForOpenGeneric overload that accepts a collection of Assembly uses this method under the covers.

Next we supply the list of non-generic subscribers to the RegisterManyForOpenGeneric overload that accepts the callback delegate and a collection of Type objects, but we concat the open generic subscribers to the collection of supplied types.

The RegisterManyForOpenGeneric will group the supplied list of non-generic types based on the closed generic ISubscribeTo<T> interface they implement (and might place on type in multiple groups if it implements multiple ISubscribeTo<T> interfaces). When the groups are constructed the callback delegate is called with the ISubscribeTo<T> service type and the grouped implementation types.

But before calling the callback delegate, this specific RegisterManyForOpenGeneric does a last thing. It will iterate the list of supplied open generic implementations (SubscribeToIMarker1<T>, 2 and 3 in this case) and will try to construct closed generic versions based on the closed generic ISubscribeTo<T> version. This is of course done based on the given type constraints. All closed generic implementations that were constructed will be concatted to the list of implementations.

This means that in the end the container.RegisterAll will be called and both non-generic and closed generic types are passed in.

The RegisterManyForOpenGeneric can't magically make up new generic types out of nothing, so it will only call the callback delegate with an closed ISubscribeTo<T> service type for which he finds a non-generic implementation that implements a closed ISubscribeTo<T>. So this means that RegisterManyForOpenGeneric won't allow us to resolve collections that consist solely of closed-generic types. For this we need the RegisterAllOpenGeneric and that's what the last statement in the RegisterSubscribers does. It registers a fallback collection with solely closed-generic types in case there is no explicit registration for a collection.

Since you call RegisterAllOpenGeneric within the RegisterSubscribers method, you will have to remove the RegisterAllOpenGeneric calls from within your unit tests. And when you do, you'll see your tests passing.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top