Основные сведения о внедрении зависимостей в .NET
В этой статье вы создадите консольное приложение .NET, которое вручную создает ServiceCollection и соответствует ServiceProvider. Вы узнаете, как зарегистрировать службы и устранить их с помощью внедрения зависимостей (DI). В этой статье используется пакет NuGet Microsoft.Extensions.DependencyInjection, чтобы продемонстрировать основы di в .NET.
Примечание.
В этой статье не используются функции универсального узла . Более подробное руководство см. в статье Об использовании внедрения зависимостей в .NET.
Начало работы
Чтобы приступить к работе, создайте консольное приложение .NET с именем DI.Basics. Некоторые из наиболее распространенных подходов к созданию консольного проекта ссылаются в следующем списке:
- Visual Studio: меню "Файл > нового > проекта ".
- Visual Studio Code и расширение набора средств разработки на C#: Обозреватель решений меню.
- .NET CLI:
dotnet new console
команда в терминале.
Необходимо добавить ссылку на пакет в файл проекта Microsoft.Extensions.DependencyInjection . Независимо от подхода, убедитесь, что проект похож на следующий XML-файл DI.Basics.csproj :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>
Основы внедрения зависимостей
Внедрение зависимостей — это шаблон проектирования, позволяющий удалять жестко закодированные зависимости и сделать приложение более управляемым и тестируемым. DI — это метод для достижения инверсии элемента управления (IoC) между классами и их зависимостями.
Абстракции для DI в .NET определяются в пакете NuGet Microsoft.Extensions.DependencyInjection.Abstractions :
- IServiceCollection: определяет контракт для коллекции дескрипторов служб.
- IServiceProvider: определяет механизм получения объекта службы.
- ServiceDescriptor: описывает службу со своим типом службы, реализацией и временем существования.
В .NET di управляется путем добавления служб и их настройки в IServiceCollection
.NET. После регистрации IServiceProvider
служб экземпляр создается путем BuildServiceProvider вызова метода. Он IServiceProvider
выступает в качестве контейнера всех зарегистрированных служб и используется для разрешения служб.
Создание example служб
Не все службы создаются одинаково. Для некоторых служб требуется новый экземпляр каждый раз, когда контейнер службы получает их (transient), а другие должны быть общими для запросов (scoped) или в течение всего времени существования приложения (singleton). Дополнительные сведения о времени существования службы см. в разделе "Время существования службы".
Аналогичным образом, некоторые службы предоставляют только конкретный тип, а другие выражаются как контракт между интерфейсом и типом реализации. Вы создаете несколько вариантов служб, которые помогут продемонстрировать эти понятия.
Создайте файл C# с именем IConsole.cs и добавьте следующий код:
public interface IConsole
{
void WriteLine(string message);
}
Этот файл определяет IConsole
интерфейс, предоставляющий один метод WriteLine
. Затем создайте новый файл C# с именем DefaultConsole.cs и добавьте следующий код:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
Предыдущий код представляет реализацию IConsole
интерфейса по умолчанию. Метод WriteLine
условно записывается в консоль на IsEnabled
основе свойства.
Совет
Именование реализации — это выбор того, что ваша команда разработки должна согласиться. Префикс Default
— это общее соглашение, указывающее реализацию интерфейса по умолчанию , но это не обязательно.
Затем создайте файл IGreetingService.cs и добавьте следующий код C#:
public interface IGreetingService
{
string Greet(string name);
}
Затем добавьте новый файл C# с именем DefaultGreetingService.cs и добавьте следующий код:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
Предыдущий код представляет реализацию IGreetingService
интерфейса по умолчанию. Реализация службы требуется в IConsole
качестве основного параметра конструктора. Метод Greet
:
- Создает заданный
greeting
name
объект. WriteLine
Вызывает метод в экземпляреIConsole
.- Возвращает вызывающий
greeting
объект.
Последняя служба для создания — это файл FarewellService.cs , добавьте следующий код C#, прежде чем продолжить:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
Представляет FarewellService
конкретный тип, а не интерфейс. Он должен быть объявлен как public
сделать его доступным для потребителей. В отличие от других типов реализации служб, объявленных как internal
и sealed
, этот код демонстрирует, что не все службы должны быть интерфейсами. Кроме того, показано, что реализации служб могут препятствовать sealed
наследованию и internal
ограничивать доступ к сборке.
Program
Обновление класса
Откройте файл Program.cs и замените существующий код следующим кодом C#:
using Microsoft.Extensions.DependencyInjection;
// 1. Create the service collection.
var services = new ServiceCollection();
// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
});
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();
// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();
// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();
// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");
Предыдущий обновленный код демонстрирует практическое руководство.
- Создайте новый экземпляр
ServiceCollection
. - Регистрация и настройка служб в следующих параметрах
ServiceCollection
:- При
IConsole
использовании перегрузки фабрики реализации возвращаетсяDefaultConsole
тип с заданнымIsEnabled
значением true. - Добавляется
IGreetingService
соответствующийDefaultGreetingService
тип реализации. - Добавляется
FarewellService
как конкретный тип.
- При
- Сборка
ServiceProvider
изServiceCollection
. IGreetingService
Разрешение служб иFarewellService
служб.- Используйте разрешенные службы для приветствия и прощание с именем человека
David
.
Если вы обновляете IsEnabled
свойство объекта DefaultConsole
false
to, Greet
методы не SayGoodbye
записывать в результирующий текст сообщений в консоль. Подобное изменение помогает продемонстрировать, что IConsole
служба внедряется в IGreetingService
службу и FarewellService
службы как зависимость, влияющая на поведение приложений.
Все эти службы регистрируются как одноэлементные, хотя для этого примера они работают одинаково, если они были зарегистрированы как transient или scoped службы.
Внимание
В этом случае exampleвремя существования службы не имеет значения, но в реальном приложении следует тщательно рассмотреть время существования каждой службы.
Запуск примера приложения
Чтобы запустить пример приложения, нажмите клавишу F5 в Visual Studio, Visual Studio Code или выполните dotnet run
команду в терминале. Когда приложение завершится, вы увидите следующие выходные данные:
Hello, David!
Goodbye, David!
Дескрипторы служб
Наиболее часто используемые API для добавления служб в ServiceCollection
методы универсального расширения со временем существования, такие как:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
Эти методы — это удобные методы, которые создают ServiceDescriptor экземпляр и добавляют его в ServiceCollection
. Это ServiceDescriptor
простой класс, описывающий службу со своим типом службы, типом реализации и временем существования. Кроме того, он может десрибе реализовывать фабрики и экземпляры.
Для каждой из служб, зарегистрированных в ней ServiceCollection
, вместо этого можно вызвать Add
метод с экземпляром напрямую ServiceDescriptor
. Рассмотрим следующие примеры:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
Предыдущий код эквивалентен тому, как IConsole
служба была зарегистрирована в .ServiceCollection
Метод Add
используется для добавления ServiceDescriptor
экземпляра IConsole
, описывающего службу. Статический метод ServiceDescriptor.Describe
делегирует различным ServiceDescriptor
конструкторам. Рассмотрим эквивалентный код для IGreetingService
службы:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
Приведенный IGreetingService
выше код описывает службу со своим типом службы, типом реализации и временем существования. Наконец, рассмотрим эквивалентный код для FarewellService
службы:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
Приведенный выше код описывает конкретный FarewellService
тип как службы, так и типы реализации. Служба зарегистрирована singleton в качестве службы.