2016-01-20 2 views
26

Я использую Unity в Регистрация по соглашению механизм по следующему сценарию:Почему тип зарегистрирован дважды, когда указан менеджер времени жизни?

public interface IInterface { } 

public class Implementation : IInterface { } 

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

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

После этого звонок, unitContainer содержит три регистраций:

  • IUnityContainer ->IUnityContainer (ок)
  • IInterface ->Implementation (ок)
  • Implementation ->Implementation (???)

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

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

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

  • IUnityContainer ->IUnityContainer (ок)
  • IInterface ->Implementation (ок)

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

После заглядывания в Unity's source code, я заметил, что есть некоторые недоразумения относительно того, как должно работать 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 добавляет одно отображение типа: IInterface ->Implementation (верный).

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

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

имя этой функции полностью ввести в заблуждение, потому что the documentation четко говорится, что:

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

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

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

[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); 

    // ... 

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

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

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

ответ

1

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

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

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

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

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

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

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

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

+0

Часть о том, что вынуждена иметь несколько экземпляров менеджера жизненного цикла по умолчанию, когда есть несколько интерфейсов, кажется сильным аргументом и единственной конкретной до сих пор причиной. Благодаря! – BartoszKP

7

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

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

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

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

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

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

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

+0

Я писал блок-тесты для загрузчика моего приложения, и это было очень удивительно. Это делает мои тесты нечитаемыми - два похожих сценария дают разные результаты. ТБХ, я не понимаю твое объяснение. Имея только одну учетную запись, вы можете использовать как менеджер по умолчанию по умолчанию, так и указанный. Вы правильно определили суть проблемы: «Единство делает предположение, что вы хотите, чтобы это поведение при разрешении интерфейсом ** и конкретным типом **» - но это вопрос: почему? Какой сценарий оправдывает это различие? Я не понимаю, почему это было бы полезно в любом случае. – BartoszKP

+0

Обновленный ответ с другой возможной причиной (плагинами) и добавил предложение об изменении модульных тестов. – TylerOhlsen

+0

Я согласен, что вы должны проверить, что вам нужно. не проверяйте о какой-либо возможной реализации. – Batavia

Смежные вопросы