DI и IoC для начинающих, часть 2
В продолжение темы по DI/IoC, мы посмотрим на сложные примеры использования Unity в нетривиальных сценариях конфигурации объектов.
Давайте в очередной раз напишем мега-сложный сервис:
Как вы уже наверное догадались, DI будет автоматически работать только для reference types. Код приведенный ниже работать не будет:
Давайте в очередной раз напишем мега-сложный сервис:
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[]>()));
Вот пока и все. Продолжение следует!



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