Неожиданное поведение регистрации Castle Windsor

#c# #castle-windsor

#c# #castle-windsor

Вопрос:

Я заметил странное поведение:

 public class Bar : IBar
{
...something here
}

public class Foo : IBar
{
...something here
}
 
 container.Register(Component.For<IBar>().ImplementedBy<Bar>());
var test1 = container.Resolve<IBar>(); //returns Bar
container.Register(Component.For<Foo>().ImplementedBy<Foo>());
var test2 = container.Resolve<IBar>(); //returns Foo
 

Почему test2 является Foo? Я не регистрировал Foo как IBar, я четко зарегистрировал его как реализацию для Foo. Я думаю, что должен быть разрешен только Bar, поскольку это единственная реализация forwaded для IBar.
Проект не мой. Могут быть некоторые странные настройки, сделанные другими разработчиками.

Комментарии:

1. Вы правы, что это не имеет смысла. Можете ли вы воспроизвести его изолированно? Очевидно, происходит что-то еще.

Ответ №1:

Очень короткий ответ заключается в том, что при выполнении изолированно приведенный выше код будет работать так, как вы ожидаете, возвращая Bar not Foo . Если он возвращается Foo , значит, есть другие регистрации компонентов, сделанные с тем же контейнером.

В этом сценарии модульный тест — это простой способ увидеть, ведет ли что-то себя так, как мы думаем, изолированно.

Этот тест проходит:

 [TestMethod]
public void registration_returns_expected_type()
{
    var container = new WindsorContainer();
    container.Register(Component.For<IBar>().ImplementedBy<Bar>());
    var test1 = container.Resolve<IBar>(); //returns Bar
    container.Register(Component.For<Foo>().ImplementedBy<Foo>());
    var test2 = container.Resolve<IBar>();
    Assert.IsInstanceOfType(test2, typeof(Bar));
}
 

Исходя из этого, я бы посмотрел, что еще регистрирует зависимости. Если вы измените приведенное выше, чтобы оба Foo и Bar были зарегистрированы как реализации IBar , он запускается и все равно возвращается Bar .

Если мы изменим Foo регистрацию на эту:

 container.Register(Component.For<IBar>().ImplementedBy<Foo>().IsDefault());
 

…затем IBar разрешается как Foo .

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

 container.Install(FromAssembly.This());
 

…или с помощью команды, которая выполняет установщики из других сборок.

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

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

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

 container.Register(Component.For<IBar>().ImplementedBy<Bar>().IsDefault());
 

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

Но проблема все еще остается. Ничто не мешает им указывать IsDefault при регистрации свои зависимости, а затем вы снова возвращаетесь к исходной точке.

(Вы увидите, что все после этого идет по кроличьей норе принятия дополнительных мер для защиты от того, что может никогда не произойти.)

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

Одна из защит — использовать именованные зависимости самостоятельно, хотя это неудобно, если они вам не нужны. (И основная проблема здесь заключается в том, что не совсем очевидно, нужны ли они вам или нет, если используются разные установщики.)

Если вы создаете отдельный установщик для нескольких компонентов, вполне вероятно, что вы имеете в виду конкретные реализации, поэтому вы можете их указать.

 public class MyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<NeedsBar>().DependsOn(Dependency.OnComponent<IBar, Foo>()));
    }
}
 

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

Это оставляет несколько вариантов, все из которых являются ошибочными:

  • Помните о потенциальной проблеме и следите за ней. Мне не нравится «быть осторожным» как решение для чего-либо, но после того, как это повлияло на меня, я обнаружил, что этого было достаточно.
  • Запустите интеграционные тесты, которые настраивают ваш контейнер с использованием всех компонентов среды выполнения, а затем проверьте контейнер на наличие дубликатов регистраций, которые являются как стандартными, так и нестандартными. Даже зная о возможности дублирования регистраций, я никогда не чувствовал необходимости делать это.
  • Надеюсь, если интерфейс настолько общий, что он используется в компонентах для всего приложения, вы можете либо избежать нескольких реализаций, либо в этом случае понимание того, как он используется, приводит к однозначным регистрациям. В других случаях просто определите отдельные интерфейсы ближе к месту, где они необходимы.

Комментарии:

1. Теперь это исчерпывающий ответ

2. Что ж, вы правы — приведенный выше пример не должен работать изолированно. Но в моем случае это каким-то образом работает. Это не проблема другой регистрации — когда я удалил Foo-ImplementedBy-Foo, плохие вещи были исправлены. Если это будет проблемой другой регистрации, удаление этой строки ничего не изменит. Я искал другие разрешающие правила, но ничего не нашел. Хотя удаление ti решило проблему, я хочу знать, что произошло, чтобы исправить это в будущем.