Содержание
В предыдущей части мы рассмотрели простой пример использования внедрения зависимостей без каких-либо специальных средств и библиотек .NET. Этот пример показывает, как минимум то, что внедрение зависимостей (dependency injection) — это набор принципов и шаблонов проектирования, а не какая-то особенность или библиотека .NET. Тем не менее, разработчики .NET/C# постарались сделать нашу с вами работу с DI наиболее комфортной и удобной, предоставив нам ряд классов и интерфейсов, позволяющих использовать возможности DI с минимальными затратами времени на разработку. И сегодня мы перепишем наш пример, используя те возможности, которые нам предлагает платформа .NET
Microsoft.Extensions.DependencyInjection
Все необходимые для работы классы и абстракции (интерфейсы) для работы с DI в .NET расположены в пакете Microsoft.Extensions.DependencyInjection. Следует отметить, что не во всех типах проектов этот пакет подключается по умолчанию. Например, в обычном консольном приложении, с которым мы работаем, такой пакет не подключен, поэтому его необходимо установить через менеджер пакетов NuGet:
В этом пакете содержатся три основных интерфейса с которыми мы будем дальше работать:
IServiceCollection
— определяет контракт для коллекции дескрипторов сервисов.IServiceProvider
— определяет механизм получения объекта сервиса.ServiceDescriptor
— описывает сервис.
Что касается терминологии, то частов можно встретить такие названия, как службы, сервисы или просто — зависимости. В контексте изучения DI эти понятия равнозначны, поэтому здесь и далее мы будем использовать название сервис.
Основной принцип работы при использовании пакета Microsoft.Extensions.DependencyInjection заключается в следующем:
- Разработанные сервисы регистрируются в
IServiceCollection
- После того, как все сервисы будут зарегистрированы мы должны получить ссылку на объект, реализующий
IServiceProvider
. Этот объект служит для получения информации о зарегистрированных службах и используется, как указано выше, для получения службы или, как ещё говорят, для разрешения зависимостей. То есть уже не мы создаем объекты-зависимости, как в прошлый раз, а именно провайдер будет брать на себя эту работу.
Попробуем переписать наш пример, используя пакет Microsoft.Extensions.DependencyInjection.
Пример использования коллекции (ServiceCollection
) и провайдера сервисов (IServiceProvider
)
Итак, на данный момент у нас уже имеется интерфейс
public interface IMessageWriter { public void SendMessage(string text); }
который и будет выступать у нас в роли сервиса. Две реализации сервиса:
public class MessageWriter: IMessageWriter { public void SendMessage(string text) { Console.WriteLine(text); } } public class ColorMessageWriter : IMessageWriter { public void SendMessage(string text) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(text); Console.ForegroundColor = ConsoleColor.White; } }
а также класс, использующий наш сервис в работе. Сейчас нам остается только продемонстрировать пример того, как будет выглядеть наше приложение с использованием новых для нас объектов.
using Microsoft.Extensions.DependencyInjection; namespace Example1 { internal class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //определяем объект какого типа необходимо создать Type type; string typeName; if (args.Length == 0) //аргументы командной строки не задана - используем тип по умолчанию typeName = "Example1.MessageWriter"; else typeName = args[0]; //пытаемся получить тип данных, объект которого необходимо создать type = Type.GetType(typeName, throwOnError: true); services.AddSingleton<Calculator>(); services.AddSingleton(implementationFactory: _ => (IMessageWriter)Activator.CreateInstance(type)); IServiceProvider serviceProvider = services.BuildServiceProvider(); Calculator? calculator = serviceProvider.GetService<Calculator>(); if (calculator != null) { calculator.Sum(10, 20); calculator.Sum(1, 2); } } } }
Рассмотрим этот пример подробно. Вначале мы создаем новую коллекцию сервисов в нашем приложении:
ServiceCollection services = new ServiceCollection();
Далее, как и в прошлой части, мы определяем тип реализации нашего сервиса IMessageWriter
, который будет использоваться при работе приложения.
//определяем объект какого типа необходимо создать Type type; string typeName; if (args.Length == 0) //аргументы командной строки не задана - используем тип по умолчанию typeName = "Example1.MessageWriter"; else typeName = args[0]; //пытаемся получить тип данных, объект которого необходимо создать type = Type.GetType(typeName, throwOnError: true);
Далее, мы добавляем сервисы в нашу коллекцию:
services.AddSingleton<Calculator>(); services.AddSingleton(implementationFactory: _ => (IMessageWriter)Activator.CreateInstance(type));
Здесь стоит обратить внимание на то, что наш класс Calculator
в приложении также выступает в роли сервиса. Сделано это для того, чтобы переложить создание нового экземпляра Calculator
полностью переложить на платформу. При регистрации сервиса IMessageWriter
мы использовали одну из версий метода AddSingleton()
в качестве параметра для которого мы передали делегат в котором создается очередной объект сервиса.
Далее, мы создаем объект провайдера сервисов:
IServiceProvider serviceProvider = services.BuildServiceProvider();
После этого действия мы можем пользоваться провайдером — запрашивать необходимые сервисы и использовать их. А далее происходит та самая «магия» Dependency Injection — мы не создаем самостоятельно никаких объектов, а просто указываем провайдеру какой сервис нам необходим для работы:
Calculator? calculator = serviceProvider.GetService<Calculator>();
в итоге, провайдер самостоятельно определит все зависимости, которые необходимы для запрашиваемого сервиса, создаст их объекты, передаст в запрашиваемый сервис и вернет нам полностью готовый для работы сервис. Ну, а далее — мы просто используем наш сервис в работе:
if (calculator != null) { calculator.Sum(10, 20); calculator.Sum(1, 2); }
Таким образом, используя ServiceCollection
и IServiceProvider
мы избавляемся от необходимости вручную определять все зависимости — теперь у нас имеется всего одна точка регистрации необходимых сервисов и все, что от нас требуется — зарегистрировать необходимые для приложения сервисы, причем, очередность регистрации сервисов к коллекции не имеет значения.
Итого
Сегодня мы рассмотрели пример Dependency Injection с использованием средств, доступных в .NET. Для использования механизмов DI мы должны создать коллекцию сервисов (ServiceCollection
) и добавить в ней необходимые для работы приложения сервисы, создать провайдер сервисов (IServiceProvider
) и запросить необходимый сервис, используя методы провайдера. Провайдер сервисов самостоятельно разрешит все зависимости в приложении и вернет нам готовый к работе сервис.