.NET

индекс
121,03

DI и IoC для начинающих, часть 2

В продолжение темы по DI/IoC, мы посмотрим на сложные примеры использования Unity в нетривиальных сценариях конфигурации объектов.

Давайте в очередной раз напишем мега-сложный сервис:

public class MyService
{
  [Dependency]
  public Random MyRandom { get; set; }
}
 

 
var uc = new UnityContainer();
var svc = uc.Resolve<MyService>();
Console.WriteLine(svc.MyRandom.Next());
Попытка создать объект типа Random через DI потерпит неудачу ибо у Random есть несколько конструкторов и Unity, несмотря на очевидность того что надо бы вызывать пустой (т.е. new Random()) этого не делает, а пытается вызвать самый «навороченный» и терпит фиаско. Как это пофиксить? Примерно вот так:

// заставляем собирать Random с дефолтным конструктором
uc.RegisterType<Random>(new InjectionConstructor());
А если бы Random был нашим классом, можно было бы еще и вот так написать:

class Random
{
  [InjectionConstructor]
  Random() {  }
  ⋮
}
Мы только что намекнули Unity что нужно использовать «пустой» конструктор. Что ж, попробуем воспльзоваться новым функционалом:

var svc  = uc.Resolve<MyService>();
var svc2 = uc.Resolve<MyService>();
Console.WriteLine(
  ReferenceEquals(svc.MyRandom, svc2.MyRandom));
В консоль будет написано значение False т.к. дефолтное поведение контейнера – каждый раз создавать новый объект. Если вы считаете, что одного Парк-энд-Миллеровского Random в принципе должно хватить на весь проект, то как получить singleton? Да очень просто:

uc.RegisterType<Random>(
  new ContainerControlledLifetimeManager(),
  new InjectionConstructor());
var svc  = uc.Resolve<MyService>();
var svc2 = uc.Resolve<MyService>();
Console.WriteLine(
  ReferenceEquals(svc.MyRandom, svc2.MyRandom));
Этот кусочек кода уже напишет в консоль True. Параметр, наследующий от LifetimeManager определяет сколько будет жить объект. От него полезно наследовать если хочется, например, чтобы для каждого потока/сессии/обращения выдывался новый объект.

Как вы уже наверное догадались, DI будет автоматически работать только для reference types. Код приведенный ниже работать не будет:

public interface IService
{
  int GetN();
}
 
public class MyService : IService
{
  public int n;
  public MyService(int n)
  {
    this.n = n;
  }
  public int GetN() { return n; }
}

var uc = new UnityContainer();
uc.RegisterType<IService, MyService>();
uc.RegisterType<int>(new InjectionConstructor(10));
var svc = uc.Resolve<IService>();
Console.Write(svc.GetN());
К сожалению, у System.Int32 не нашлось конструктора и поэтому этот код, который кстате компилируется без проблем, работать не будет. На самом деле, мы просто выбрали неверный аттрибут – вместо того чтобы манипулировать созданием Int32, в данном случае нужно управлять созданием IService:

uc.RegisterType<IService, MyService>(
  new InjectionConstructor(
    new InjectionParameter(10)));
Все это были достаточно очевидные манипуляции конструкторов, посмотрим на пример посложнее. Допустим у вас есть два сервиса, и оба реализуют IService:

public interface IService
{
  void DoSomething();
}
public class MyService : IService
{
  public void DoSomething()
  {
    Console.WriteLine("My service");
  }
}
public class OtherService : IService
{
  public void DoSomething()
  {
    Console.WriteLine("Other service");
  }
}
Теперь создадим класс который потребляет эти сервисы:

public class Consumer
{
  IService[] services;
  public Consumer(IService[] services)
  {
    this.services = services;
  }
  public void DoEverything() 
  {
    foreach (var s in services)
     s.DoSomething();
  }
}
Попытка зарезолвить Consumer и вызвать DoEverything не приведет ни к чему – Unity понятия не имеет, что неплохо было бы зарезолвить IService как вытяжку всех зарегистрированных типов IService, и поэтому в конструктор передаст new IService[0]. Контейнеру опять приходится помогать:

uc.RegisterType<Consumer>(new InjectionConstructor(
  new ResolvedParameter<IService[]>()));
Вот пока и все. Продолжение следует!
+4
30 июня 2009, 21:43
15

комментарии (18)

+2
XuMiX #
как-то все слишком сложно по сравнению с MEF
0
spycode #
Спасибо. Очень полезная статья. По больше бы таких.
0
sse #
Вы знаете, при всей моей любви к принципу IoC, простые примеры с контейнерами, к сожалению, явно не идут на пользу популяризации такой модели сборки ПО из компонент :(
0
mezastel #
Почему?
+1
sse #
Потому что для таких простых примеров

var svc = new MyService(10);

выглядит куда лучше, чем

var uc = new UnityContainer();
uc.RegisterType<IService, MyService>();
uc.RegisterType(new InjectionConstructor(10));
var svc = uc.Resolve();

Пользователь скажет, что он «в гробу видел ваши контейнеры», и будет прав =)

P.S. Кстати, как в свете описанного примера будет выглядеть такой код, если его переписать на использование Unity?

var svc10 = new MyService(10);
var svc15 = new MyService(15);
+2
xlam #
На мой взгляд, человек, заинтересовавшийся IoC, через призму небольших примеров, видит то, как он будет применять IoC в своих реальных проектах, и его вряд ли отпугнут такие примеры.
–1
sse #
Вот и вы употребляете «отпугнуть», что как бэ намекаэ… :)

>> P.S. Кстати, как в свете описанного примера будет выглядеть такой код, если его переписать на использование Unity?

А на этот вопрос вы не могли бы ответить?
0
ulu #
–1
acerv #
:))) этапять
+1
xlam #
не могли бы вы к этой статье и к последующим, добавить ссылки на предыдущие части?!
0
mezastel #
Добавил
0
xlam #
спасибо.
0
savamura #
А подскажите, пожалуйста, как сконфигурировать только через xml синглтон, реализующий два интерфейса?

class Impl : IA, IB {}

Я пробовал так:
<register type="Impl">
<lifetime type="singleton" />
</register>

<register type="IA" mapTo="Impl"/>
<register type="IB" mapTo="Impl"/>

Интстансы разные получились отчего-то :(
Кодом это просто сделать… а хочется декларативненько, через xml
0
andrexx #
Декларативность в XML на практике бесполезна и аукается потом кучей проблем при сопровождении. Поэтому если хочется DI/IoC то конфигурировать нужно через код. А если все таки нужна динамическая подмена компонентов — тогда смотреть MEF. А все эти страшные xml конфиги — ошибка природы.
0
savamura #
Сделал в итоге дефолтную конфигурацию в коде, с возможностью переопределить в xml в экстренном случае.
0
constructor #
пытался реализовать последний пример и не получается: myService.Randoms показывает Random[0]
где ошибка?

IUnityContainer container = new UnityContainer();
container.RegisterType(
new InjectionConstructor()); // use default constructor for new Random()
container.RegisterType(
new InjectionConstructor()); // use default constructor for new Random()
container.RegisterType
(new InjectionConstructor(
new ResolvedParameter()));

MyServiceWithArray myService = container.Resolve();
foreach (Random random in myService.Randoms)
{
Console.WriteLine(«Next: » + random.Next());
}
}

public class MyServiceWithArray
{
public Random[] Randoms { get; private set; }
public MyServiceWithArray(Random[] randoms)
{
Randoms = randoms;
}
}
public class MyRandom: Random
{
}

0
constructor #
пытался реализовать последний пример и не получается: myService.Randoms показывает Random[0]
где ошибка?

IUnityContainer container = new UnityContainer();
container.RegisterType(
new InjectionConstructor()); // use default constructor for new Random()
container.RegisterType(
new InjectionConstructor()); // use default constructor for new Random()
container.RegisterType
(new InjectionConstructor(
new ResolvedParameter()));

MyServiceWithArray myService = container.Resolve();
foreach (Random random in myService.Randoms)
{
Console.WriteLine(«Next: » + random.Next());
}

public class MyServiceWithArray
{
public Random[] Randoms { get; private set; }
public MyServiceWithArray(Random[] randoms)
{
Randoms = randoms;
}
}
public class MyRandom: Random
{
}

0
vgrinin #
Два вопроса:
1) James Covax действительно очень доходчиво объяснил, что такое IoC, вплоть до самостоятельной реализации простейшего IoC-контейнера. Он использовал так называемый static gateway, то есть создал статическую обертку для UnityContainer, чтобы в любом конструкторе иметь доступ ко всем зарегистрированным в контейнере типам. Насколько эта практика хороша?
2) Подскажите, как реализовать вот какую вещь. Пусть у нас есть сервис MyService и потребитель сервиса Consumer.
при этом
class Consumer{
public string DoSomething(string arg){
MyService service = new MyService(arg);
return service.GetSomething();
}
}
Подскажите, как приспособить этот код под IoC? У меня вызвал затруднение вот этот внутренний вызов конструктора с аргументами.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.