В продолжение темы по DI/IoC, мы посмотрим на сложные примеры использования Unity в нетривиальных сценариях конфигурации объектов.
Давайте в очередной раз напишем мега-сложный сервис:
Как вы уже наверное догадались, DI будет автоматически работать только для reference types. Код приведенный ниже работать не будет:
Давайте в очередной раз напишем мега-сложный сервис:
public class MyService<br/>
{<br/>
[Dependency]<br/>
public Random MyRandom { get; set; }<br/>
}<br/>
<br/>
⋮<br/>
<br/>
var uc = new UnityContainer();<br/>
var svc = uc.Resolve<MyService>();<br/>
Console.WriteLine(svc.MyRandom.Next());<br/>
Попытка создать объект типа Random
через DI потерпит неудачу ибо у Random
есть несколько конструкторов и Unity, несмотря на очевидность того что надо бы вызывать пустой (т.е. new Random()
) этого не делает, а пытается вызвать самый «навороченный» и терпит фиаско. Как это пофиксить? Примерно вот так:// заставляем собирать Random с дефолтным конструктором
uc.RegisterType<Random>(new InjectionConstructor());<br/>
А если бы Random
был нашим классом, можно было бы еще и вот так написать:class Random<br/>
{<br/>
[InjectionConstructor]<br/>
Random() { }<br/>
⋮<br/>
}<br/>
Мы только что намекнули Unity что нужно использовать «пустой» конструктор. Что ж, попробуем воспльзоваться новым функционалом:var svc = uc.Resolve<MyService>();<br/>
var svc2 = uc.Resolve<MyService>();<br/>
Console.WriteLine(<br/>
ReferenceEquals(svc.MyRandom, svc2.MyRandom));<br/>
В консоль будет написано значение False
т.к. дефолтное поведение контейнера – каждый раз создавать новый объект. Если вы считаете, что одного Парк-энд-Миллеровского Random
в принципе должно хватить на весь проект, то как получить singleton? Да очень просто:uc.RegisterType<Random>(<br/>
new ContainerControlledLifetimeManager(),<br/>
new InjectionConstructor());<br/>
var svc = uc.Resolve<MyService>();<br/>
var svc2 = uc.Resolve<MyService>();<br/>
Console.WriteLine(<br/>
ReferenceEquals(svc.MyRandom, svc2.MyRandom));<br/>
Этот кусочек кода уже напишет в консоль True
. Параметр, наследующий от LifetimeManager
определяет сколько будет жить объект. От него полезно наследовать если хочется, например, чтобы для каждого потока/сессии/обращения выдывался новый объект.Как вы уже наверное догадались, DI будет автоматически работать только для reference types. Код приведенный ниже работать не будет:
public interface IService<br/>
{<br/>
int GetN();<br/>
}<br/>
<br/>
public class MyService : IService<br/>
{<br/>
public int n;<br/>
public MyService(int n)<br/>
{<br/>
this.n = n;<br/>
}<br/>
public int GetN() { return n; }<br/>
}<br/>
⋮<br/>
var uc = new UnityContainer();<br/>
uc.RegisterType<IService, MyService>();<br/>
uc.RegisterType<int>(new InjectionConstructor(10));<br/>
var svc = uc.Resolve<IService>();<br/>
Console.Write(svc.GetN());<br/>
К сожалению, у System.Int32
не нашлось конструктора и поэтому этот код, который кстате компилируется без проблем, работать не будет. На самом деле, мы просто выбрали неверный аттрибут – вместо того чтобы манипулировать созданием Int32
, в данном случае нужно управлять созданием IService
:uc.RegisterType<IService, MyService>(<br/>
new InjectionConstructor(<br/>
new InjectionParameter(10)));<br/>
Все это были достаточно очевидные манипуляции конструкторов, посмотрим на пример посложнее. Допустим у вас есть два сервиса, и оба реализуют IService
:public interface IService<br/>
{<br/>
void DoSomething();<br/>
}<br/>
public class MyService : IService<br/>
{<br/>
public void DoSomething()<br/>
{<br/>
Console.WriteLine("My service");<br/>
}<br/>
}<br/>
public class OtherService : IService<br/>
{<br/>
public void DoSomething()<br/>
{<br/>
Console.WriteLine("Other service");<br/>
}<br/>
}<br/>
Теперь создадим класс который потребляет эти сервисы:public class Consumer<br/>
{<br/>
IService[] services;<br/>
public Consumer(IService[] services)<br/>
{<br/>
this.services = services;<br/>
}<br/>
public void DoEverything() <br/>
{<br/>
foreach (var s in services)<br/>
s.DoSomething();<br/>
}<br/>
}<br/>
Попытка зарезолвить Consumer
и вызвать DoEverything
не приведет ни к чему – Unity понятия не имеет, что неплохо было бы зарезолвить IService
как вытяжку всех зарегистрированных типов IService
, и поэтому в конструктор передаст new IService[0]
. Контейнеру опять приходится помогать:uc.RegisterType<Consumer>(new InjectionConstructor(<br/>
new ResolvedParameter<IService[]>()));<br/>
Вот пока и все. Продолжение следует!