다음을 통해 공유


.NET의 종속성 주입 기본 사항 이해

이 문서에서는 ServiceCollection 및 해당하는 ServiceProvider를 수동으로 만드는 .NET 콘솔 앱을 만듭니다. DI(종속성 주입)를 사용하여 서비스를 등록하고 해결하는 방법을 알아봅니다. 이 문서에서는 Microsoft.Extensions.DependencyInjection NuGet 패키지를 사용하여 .NET에서 DI의 기본 사항을 보여 줍니다.

참고

이 문서에서는 일반 호스트 기능을 활용하지 않습니다. 좀 더 포괄적인 가이드는 .NET에서의 종속성 주입 사용을 참조하세요.

시작하기

시작하려면 DI.Basics라는 새 .NET 콘솔 애플리케이션을 만듭니다. 콘솔 프로젝트를 만드는 가장 일반적인 방법 중 일부는 다음 목록에서 참조할 수 있습니다.

프로젝트 파일에서 Microsoft.Extensions.DependencyInjection에 패키지 참조를 추가해야 합니다. 접근 방식에 관계없이 프로젝트가 DI.Basics.csproj 파일의 다음 XML과 유사한지 확인합니다.

<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.3" />
  </ItemGroup>

</Project>

종속성 주입 기본 사항

종속성 주입은 하드 코드된 종속성을 제거하고 애플리케이션의 유지 관리 및 테스트 효율을 높일 수 있게 해 주는 디자인 패턴입니다. DI는 클래스와 해당 종속성 간에 IoC(Inversion of Control)를 달성하기 위한 기술입니다.

.NET에서 DI에 대한 추상화는 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 패키지에 정의됩니다.

  • IServiceCollection: 서비스 설명자 컬렉션에 대한 계약을 정의합니다.
  • IServiceProvider: 서비스 개체를 검색하는 메커니즘을 정의합니다.
  • ServiceDescriptor: 서비스 종류, 구현 및 수명을 포함하여 서비스에 대해 설명합니다.

.NET에서 DI는 IServiceCollection에서 서비스를 추가하고 구성하는 방식으로 관리됩니다. 서비스가 등록된 후 IServiceProvider 인스턴스는 BuildServiceProvider 메서드를 호출하여 빌드됩니다. IServiceProvider는 등록된 모든 서비스의 컨테이너 역할을 하며 서비스를 확인하는 데 사용됩니다.

예제 서비스 만들기

모든 서비스가 동일하게 만들어지는 것은 아닙니다. 일부 서비스에는 서비스 컨테이너가 해당 인스턴스(일시적)를 받을 때마다 새 인스턴스가 필요하지만, 다른 인스턴스는 요청(범위가 지정된) 또는 앱의 전체 수명(싱글톤)에서 공유해야 합니다. 서비스 수명에 대한 자세한 내용은 서비스 수명을 참조하세요.

마찬가지로 일부 서비스는 구체적인 형식만 노출하고 또 일부 서비스는 인터페이스와 구현 형식 간의 계약으로 표현됩니다. 이러한 개념을 설명하는 데 도움이 되도록 여러 가지 서비스 변형을 만듭니다.

IConsole.cs라는 새 C# 파일을 만들고 다음 코드를 추가합니다.

public interface IConsole
{
    void WriteLine(string message);
}

이 파일은 단일 메서드 IConsole을 노출하는 WriteLine 인터페이스를 정의합니다. 그런 다음 DefaultConsole.cs라는 새 C# 파일을 만들고 다음 코드를 추가합니다.

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);
}

그런 다음 DefaultGreetingService.cs라는 새 C# 파일을 추가하고 다음 코드를 추가합니다.

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으로 선언해야 합니다. internalsealed로 선언된 다른 서비스 구현 형식과 달리 이 코드는 모든 서비스가 인터페이스일 필요는 없음을 보여 줍니다. 서비스를 구현할 때 상속을 방지하기 위한 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를 빌드합니다.
  • IGreetingServiceFarewellService 서비스를 해결합니다.
  • 확인된 서비스를 사용하여 David라는 사람에게 인사하고 작별 인사를 합니다.

IsEnabledDefaultConsole 속성을 false로 업데이트하는 경우 GreetSayGoodbye 메서드는 결과 메시지에 콘솔에 쓰는 것을 생략합니다. 이와 같은 변경은 서비스 서비스종속성으로 주입되어 해당 앱의 동작에 영향을 미친다는 것을 보여주는 데 도움이 됩니다.

이러한 모든 서비스는 싱글톤으로 등록되지만, 이 샘플에서는 임시 또는 범위가 지정된 서비스로 등록되어도 동일하게 작동합니다.

중요

이 모순된 예제에서는 서비스 수명이 중요하지 않지만 실제 애플리케이션에서는 각 서비스의 수명을 신중하게 고려해야 합니다.

샘플 앱 실행

샘플 앱을 실행하려면 Visual Studio 또는 Visual Studio Code에서 F5를 누르거나 터미널에서 dotnet run 명령을 실행합니다. 앱이 완료되면 다음 출력이 표시됩니다.

Hello, David!
Goodbye, David!

서비스 설명자

서비스를 ServiceCollection에 추가하는 데 가장 일반적으로 사용되는 API는 다음과 같은 수명 이름이 지정된 제네릭 확장 메서드입니다.

  • 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 형식을 서비스 및 구현 형식으로 설명합니다. 서비스는 싱글톤 서비스로 등록됩니다.

참고