Подтвердить что ты не робот

Почему тип регистрируется дважды, когда указан менеджер времени жизни?

Я использую Unity Register по механизму соглашения в следующем сценарии:

public interface IInterface { }

public class Implementation : IInterface { }

Учитывая класс Implementation и его интерфейс, я запускаю RegisterTypes следующим образом:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) },
    WithMappings.FromAllInterfaces,
    WithName.Default,
    WithLifetime.ContainerControlled);

После этого вызова unitContainer содержит три регистрации:

  • IUnityContainerIUnityContainer (ok)
  • IInterfaceImplementation (ok)
  • ImplementationImplementation (???)

Когда я меняю вызов следующим образом:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) },
    WithMappings.FromAllInterfaces,
    WithName.Default);

Контейнер содержит только две регистрации:

  • IUnityContainerIUnityContainer (ok)
  • IInterfaceImplementation (ok)

(это желаемое поведение).

После просмотра в исходного кода Unity я заметил, что есть некоторые недоразумения о том, как IUnityContainer.RegisterType должен работать.

Метод RegisterTypes работает следующим образом (комментарии указывают, каковы значения в приведенных выше сценариях):

foreach (var type in types)
{
    var fromTypes = getFromTypes(type); // { IInterface }
    var name = getName(type);           // null
    var lifetimeManager = getLifetimeManager(type); // null or ContainerControlled
    var injectionMembers = getInjectionMembers(type).ToArray(); // null

    RegisterTypeMappings(container, overwriteExistingMappings, type, name, fromTypes, mappings);

    if (lifetimeManager != null || injectionMembers.Length > 0)
    {
        container.RegisterType(type, name, lifetimeManager, injectionMembers);   // !
    }
}

Поскольку fromTypes не пуст, RegisterTypeMappings добавляет одно типа: IInterfaceImplementation (правильно).

Тогда, если lifetimeManager не является нулевым, код пытается изменить диспетчер времени жизни следующим вызовом:

container.RegisterType(type, name, lifetimeManager, injectionMembers);

Это имя функции полностью вводит в заблуждение, потому что документация четко заявляет, что:

RegisterType a LifetimeManager для данного типа и имени в контейнере. Для этого типа не выполняется сопоставление типов.

К сожалению, не только имя вводит в заблуждение, но и документация неверна. При отладке этого кода я заметил, что когда в сценариях, представленных выше, нет отображения из type (Implementation), он добавляется (как typetype) и почему мы заканчиваем с тремя регистрациями в первом сценарии.

Я загрузил источники Unity, чтобы исправить эту проблему, но я нашел следующее unit test:

[TestMethod]
public void RegistersMappingAndImplementationTypeWithLifetimeAndMixedInjectionMembers()
{
    var container = new UnityContainer();
    container.RegisterTypes(new[] { typeof(MockLogger) }, getName: t => "name", getFromTypes: t => t.GetTypeInfo().ImplementedInterfaces, getLifetimeManager: t => new ContainerControlledLifetimeManager());

    var registrations = container.Registrations.Where(r => r.MappedToType == typeof(MockLogger)).ToArray();

    Assert.AreEqual(2, registrations.Length);

    // ...

- это почти точно мой случай и приводит к моему вопросу:

Почему это ожидалось? Является концептуальной ошибкой, созданной unit test для соответствия существующему поведению, но не обязательно правильной, или мне не хватает чего-то важного?

Я использую Unity v4.0.30319.

4b9b3361

Ответ 1

Одной из причин, по которым я могу думать о текущем поведении, является то, что он повторно использует существующую функциональность/реализацию Unity и делает процедуру регистрации по соглашению довольно простой в реализации.

Существующая реализация Unity, о которой я думаю, - это разделение сопоставления типов и плана сборки на разные политики, поэтому, если вы работаете над исходным кодом Unity, который будет обычным способом думать о вещах. Кроме того, из того, что я читал в прошлом, свойство Registrations похоже на то, что оно считается гражданином второго класса и не предназначено для использования гораздо больше, чем отладка, поэтому наличие другой регистрации, возможно, не показалось бы большое дело. Возможно, эти два момента были частью решения?

Помимо дополнительного элемента в коллекциях регистрации функция работает в этом случае.

Тогда, если lifetimeManager не является нулевым, код пытается изменить диспетчер времени жизни следующим вызовом

Метод RegisterTypes фактически не устанавливает a lifetimeManager. Если не указано lifetimeManager, то конкретный целевой тип явно не зарегистрирован, а Unity полагается на внутреннее поведение по умолчанию при создании объекта (в данном случае по умолчанию lifetimeManager TransientLifetimeManager).

Но если указано что-то, что может потенциально переопределить значения по умолчанию (т.е. a lifetimeManager или InjectionMembers), тогда конкретный тип будет явно зарегистрирован.

Должна быть обеспечена возможность регистрации по Конвенции для работы без дополнительной регистрации. Однако есть некоторые сложности для рассмотрения. Если конкретный тип реализует множество интерфейсов, может существовать несколько картографических регистраций для конкретного типа, но для каждой регистрации потребуется собственный экземпляр менеджера жизненного цикла (их нельзя использовать повторно). Таким образом, вы можете создавать новые экземпляры менеджера жизненного цикла для каждой регистрации картографирования, но (я считаю) будет использоваться только последний менеджер времени жизни. Итак, чтобы избежать ненужного создания объекта, возможно, назначить диспетчер времени жизни при отображении последнего типа? Это всего лишь один сценарий, который произошел со мной - есть множество сценариев и комбинаций, которые следует учитывать.

Итак, я считаю, что текущая реализация - это простой способ реализовать эту функцию, не имея необходимости специально обрабатывать случаи с краем, единственным побочным эффектом которого является дополнительная регистрация. Я бы предположил, что это, вероятно, было частью мышления того времени (но меня там не было, так что это просто предположение).

Ответ 2

Нам нужно обратиться к оригинальным разработчикам, чтобы убедиться, но это то, что я могу только предположить, почему он был закодирован таким образом...

Unity имеет функцию, позволяющую разрешать конкретные классы, даже если тип не был зарегистрирован. В этом случае Unity должно сделать предположение о том, что вы хотите, чтобы менеджер жизненных циклов по умолчанию (переходный) и не был членом инъекции для этого конкретного типа. Если вам не нравятся эти политики по умолчанию, вам необходимо зарегистрировать конкретный тип самостоятельно и указать свою настройку.

Итак, следуя этому методу, когда вы вызываете RegisterTypes, и вы указываете менеджера жизненного цикла и/или члена инъекции, Unity делает предположение, что вы хотите, чтобы это поведение было разрешено интерфейсом и конкретным типом. Но если вы не укажете эти политики, Unity не нуждается в конкретной регистрации, потому что при разрешении конкретного типа оно будет возвращаться к по умолчанию.

Единственное другое объяснение, которое я могу придумать, - это ради плагинов. InjectionMember является расширяемым, поэтому Unity думает, что вы можете передавать настраиваемое поведение для плагина. И поэтому он делает предположение, что вы можете захотеть, чтобы пользовательское поведение для плагина применялось как к интерфейсу, так и к конкретному, когда вы регистрируетесь по соглашению.


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

С точки зрения unit test я бы сказал, что вы выходите за рамки того, что вы пытаетесь проверить. Если вы начнете использовать плагины, начнется появление еще нескольких регистраций (например, политики перехвата и поведения). Это просто добавит проблему, которую вы видите сейчас из-за того, как вы тестируете. Я бы рекомендовал вам изменить свои тесты, чтобы убедиться, что зарегистрирован только белый список типов, и игнорировать все остальное. Наличие конкретных (или других дополнительных регистраций) является доброкачественным для вашей заявки.

(например, поместите свои интерфейсы в белый список и утвердите, что каждый из них зарегистрирован и игнорирует тот факт, что бетон зарегистрирован)