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


Основные сведения о внедрении зависимостей в .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="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 falseto, 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 в качестве службы.

См. также