Pull to refresh

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

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

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

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/>
Вот пока и все. Продолжение следует!
Tags: .netccsharpiocdiunity
Hubs: .NET
Total votes 12: ↑8 and ↓4 +4
Comments 18
Comments Comments 18

Popular right now