Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье вы создадите консольное приложение .NET, которое вручную создает ServiceCollection и соответствующий ServiceProvider. Вы узнаете, как зарегистрировать службы и устранить их с помощью внедрения зависимостей (DI). В этой статье используется пакет NuGet Microsoft.Extensions.DependencyInjection, чтобы продемонстрировать основы внедрения зависимостей в .NET.
Примечание.
Эта статья не использует возможности Generic Host. Более подробное руководство см. в статье Об использовании внедрения зависимостей в .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="9.0.4" />
</ItemGroup>
</Project>
Основы внедрения зависимостей
Внедрение зависимостей — это шаблон проектирования, позволяющий удалять жестко закодированные зависимости и сделать приложение более управляемым и тестируемым. DI — это метод для достижения инверсии управления (IoC) между классами и их зависимостями.
Абстракции для DI в .NET определяются в пакете NuGet Microsoft.Extensions.DependencyInjection.Abstractions
- IServiceCollection: определяет контракт для коллекции дескрипторов сервисов.
- IServiceProvider: определяет механизм получения объекта службы.
- ServiceDescriptor: описывает службу со своим типом службы, реализацией и временем существования.
В .NET DI управляется путем добавления служб и их настройки в IServiceCollection
. После регистрации служб создается экземпляр IServiceProvider
с помощью вызова метода BuildServiceProvider. Он IServiceProvider
выступает в качестве контейнера всех зарегистрированных служб и используется для разрешения служб.
Создание примеров служб
Не все услуги одинаковы. Для некоторых сервисов требуется новый экземпляр каждый раз, когда контейнер сервиса получает их (транзиентные), а другие должны совместно использоваться между запросами (скопированные) или в течение всего времени существования приложения (синглтон). Дополнительные сведения о времени существования службы см. в разделе "Время существования службы".
Аналогичным образом, некоторые службы предоставляют только конкретный тип, а другие выражаются как контракт между интерфейсом и типом реализации. Вы создаете несколько вариантов служб, которые помогут продемонстрировать эти понятия.
Создайте файл 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
, методы Greet
и SayGoodbye
не будут записывать результативные сообщения в консоль. Подобное изменение помогает продемонстрировать, что IConsole
служба внедряется в и IGreetingService
службы как FarewellService
, которая влияет на поведение приложений.
Все эти сервисы зарегистрированы как одиночные, хотя для этого примера они работают одинаково, если были зарегистрированы как временные или ограниченные сервисы.
Внимание
В этом приведенном примере время существования службы не имеет значения, но в реальном приложении следует тщательно рассмотреть время существования каждой службы.
Запуск примера приложения
Чтобы запустить пример приложения, нажмите клавишу 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
тип как службы, так и типы реализации. Сервис зарегистрирован как одиночный сервис.