Поделиться через


Основные сведения о внедрении зависимостей в .NET

В этой статье вы создадите консольное приложение .NET, которое вручную создает ServiceCollection и соответствует ServiceProvider. Вы узнаете, как зарегистрировать службы и устранить их с помощью внедрения зависимостей (DI). В этой статье используется пакет NuGet Microsoft.Extensions.DependencyInjection, чтобы продемонстрировать основы di в .NET.

Примечание.

В этой статье не используются функции универсального узла . Более подробное руководство см. в статье Об использовании внедрения зависимостей в .NET.

Начало работы

Чтобы приступить к работе, создайте консольное приложение .NET с именем DI.Basics. Некоторые из наиболее распространенных подходов к созданию консольного проекта ссылаются в следующем списке:

Необходимо добавить ссылку на пакет в файл проекта 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.1" />
  </ItemGroup>

</Project>

Основы внедрения зависимостей

Внедрение зависимостей — это шаблон проектирования, позволяющий удалять жестко закодированные зависимости и сделать приложение более управляемым и тестируемым. DI — это метод для достижения инверсии элемента управления (IoC) между классами и их зависимостями.

Абстракции для DI в .NET определяются в пакете NuGet Microsoft.Extensions.DependencyInjection.Abstractions :

  • IServiceCollection: определяет контракт для коллекции дескрипторов служб.
  • IServiceProvider: определяет механизм получения объекта службы.
  • ServiceDescriptor: описывает службу со своим типом службы, реализацией и временем существования.

В .NET di управляется путем добавления служб и их настройки в IServiceCollection.NET. После регистрации 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:

  • Создает заданный greetingnameобъект.
  • 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 свойство объекта DefaultConsolefalseto, 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 тип как службы, так и типы реализации. Сервис зарегистрирован как одиночный сервис.

См. также