Как правильно использовать SimpleInjector - RegisterAllOpenGeneric

Я не могу понять, как правильно использовать RegisterAllOpenGeneric

У меня есть эти простые определения:

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

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

и обычный Subscriber:

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

который зарегистрирован с этим кодом:

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

и возвращается при вызове:

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

Пока все хорошо.

PocoB также реализует интерфейс IMarker, и я пытаюсь создать связь с Subscriber по AnEventOf<IMarker>>, которая также будет возвращена при вызове GetAllInstances<ISubscribeTo<AnEventOf<PocoB>>>();.

Я пробовал 3 разных определения:

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
{
}

Вот различные методы тестирования, которые я написал - ни один из тестов не работает, все они возвращают только 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));
}

Нужно ли мне делать больше, чтобы правильно зарегистрировать все открытые дженерики (например, SubscribeToIMarker1)?

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

person qujck    schedule 25.09.2013    source источник


Ответы (1)


RegisterAllOpenGeneric является коллекцией, эквивалентной методу RegisterOpenGeneric. Это означает, что вы можете зарегистрировать набор открытых универсальных типов как коллекцию. Однако, как и в случае с методом RegisterOpenGeneric, коллекция, зарегистрированная с помощью RegisterAllOpenGeneric, будет разрешена только при отсутствии явной регистрации.

В вашем случае вы делаете явную регистрацию коллекции, вызывая container.RegisterAll(Type, Type[]). Поскольку вы зарегистрировали этот тип явно, зарегистрированная коллекция открытых универсальных типов не будет создана.

Это нормально, но поскольку ваши явные регистрации коллекций не содержат никаких открытых универсальных типов, разрешенная коллекция будет содержать только эти зарегистрированные неуниверсальные типы. Регистрация набора открытых универсальных типов с использованием RegisterAllOpenGeneric не включает их автоматически ни в какие регистрации RegisterAll.

Таким образом, решение состоит в том, чтобы зарегистрировать и открытые универсальные типы. Однако вы не можете добавить открытые универсальные типы в метод RegisterAll, так как этот метод принимает только закрытые универсальные типы и неуниверсальные типы. Хитрость заключается в использовании перегрузки RegisterManyForOpenGeneric, которая принимает как делегат обратного вызова, так и набор Type. Эта перегрузка принимает открытые универсальные типы и распространяет закрытые универсальные версии обратно делегату обратного вызова.

Вот как должен выглядеть ваш метод RegisterSubscribers:

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

Здесь происходит то, что вместо вызова метода RegisterManyForOpenGeneric, который принимает набор объектов Assembly, мы вызываем метод OpenGenericBatchRegistrationExtensions.GetTypesToRegister для получения списка неуниверсальных подписчиков. Перегрузка RegisterManyForOpenGeneric, которая принимает набор Assembly, использует этот метод скрыто.

Затем мы передаем список неуниверсальных подписчиков перегрузке RegisterManyForOpenGeneric, которая принимает делегат обратного вызова и набор объектов Type, но мы объединяем открытых универсальных подписчиков с набором предоставленных типов.

RegisterManyForOpenGeneric сгруппирует предоставленный список необобщенных типов на основе закрытого универсального интерфейса ISubscribeTo<T>, который они реализуют (и может поместить тип в несколько групп, если он реализует несколько интерфейсов ISubscribeTo<T>). Когда группы созданы, делегат обратного вызова вызывается с типом службы ISubscribeTo<T> и сгруппированными типами реализации.

Но перед вызовом делегата обратного вызова этот конкретный RegisterManyForOpenGeneric выполняет последнее действие. Он будет повторять список предоставленных открытых универсальных реализаций (в данном случае SubscribeToIMarker1<T>, 2 и 3) и попытается создать закрытые универсальные версии на основе закрытой универсальной версии ISubscribeTo<T>. Это, конечно, делается на основе заданных ограничений типа. Все закрытые универсальные реализации, которые были созданы, будут объединены в список реализаций.

Это означает, что в конце концов будет вызван container.RegisterAll, и будут переданы как неуниверсальные, так и закрытые универсальные типы.

RegisterManyForOpenGeneric не может волшебным образом создавать новые универсальные типы из ничего, поэтому он будет вызывать делегат обратного вызова только с закрытым ISubscribeTo<T> типом службы, для которого он находит неуниверсальную реализацию, реализующую закрытый ISubscribeTo<T>. Это означает, что RegisterManyForOpenGeneric не позволит нам разрешать коллекции, состоящие исключительно из закрытых универсальных типов. Для этого нам нужен RegisterAllOpenGeneric, и это то, что делает последний оператор в RegisterSubscribers. Он регистрирует резервную коллекцию только с закрытыми универсальными типами на случай, если для коллекции нет явной регистрации.

Поскольку вы вызываете RegisterAllOpenGeneric в методе RegisterSubscribers, вам придется удалить вызовы RegisterAllOpenGeneric из ваших модульных тестов. И когда вы это сделаете, вы увидите, что ваши тесты пройдены.

person Steven    schedule 25.09.2013
comment
О, простое решение! Из интереса - из 3 открытых универсальных определений в вопросе возвращается только первое `SubscribeToIMarker1‹TMarker› : ISubscribeTo‹AnEventOf‹TMarker››' (это не проблема, поскольку оно имеет смысл). - person qujck; 25.09.2013
comment
@qujck: Если это единственное, что было возвращено, то это потому, что другие не совпадают из-за ограничений их универсального типа. - person Steven; 25.09.2013