C# 프로젝트에서 DI(Dependency Injection)를 구성할 때 가장 핵심이 되는 두 가지 요소가 있습니다.
바로 services와 provider입니다.
이 글에서는 services와 provider가 각각 어떤 역할을 하는지, 그리고 실제 사용 방법을 체계적으로 정리합니다.
1. services란 무엇인가?
1.1 IServiceCollection 타입
var services = new ServiceCollection();
- services는 IServiceCollection 타입입니다.
- 어떤 인터페이스에 어떤 구현체를 연결할지를 등록하는 DI 컨테이너 구성용 객체입니다.
- 단순한 객체 리스트가 아니라, 의존성 주입 규칙을 등록하는 역할을 합니다.
2. services를 사용하는 이유
DI를 제대로 활용하려면,
"필요한 객체를 누가 생성하고 주입해줄까?"를 해결해야 합니다.
이를 위해 services를 사용하여 다음을 설정합니다.
2.1 필요한 객체 등록
인터페이스와 구현체를 연결합니다.
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddTransient<MyController>();
- ILogger를 요구하는 클래스에는 ConsoleLogger를 주입하도록 등록합니다.
- MyController는 필요할 때마다 새 인스턴스를 생성하도록 등록합니다.
2.2 객체 생명주기(Lifetime) 관리
DI 컨테이너는 객체를 언제 생성하고 얼마나 유지할지를 설정할 수 있습니다.
생명주기 | 설명 |
Singleton | 애플리케이션 전체에서 하나만 생성 |
Transient | 요청할 때마다 새 인스턴스를 생성 |
Scoped | 웹 요청 단위로 새 인스턴스 생성 (ASP.NET Core 전용) |
3. services와 BuildServiceProvider()
services에 객체를 등록한 뒤에는,
실제 객체를 주입할 수 있도록 ServiceProvider를 생성해야 합니다.
3.1 전체 흐름 예시
var services = new ServiceCollection();
// 1. 인터페이스와 구현체 등록
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddTransient<MyController>();
// 2. 서비스 프로바이더 생성
var provider = services.BuildServiceProvider();
// 3. 객체 요청 (자동 주입 완료)
var controller = provider.GetRequiredService<MyController>();
controller.Run();
- BuildServiceProvider()를 호출하면 등록된 서비스 규칙을 기반으로 실제 객체를 생성할 준비를 합니다.
- GetRequiredService<MyController>()를 호출하면 필요한 의존성(ILogger)까지 찾아서 자동 주입합니다.
4. provider 주요 함수 정리
provider를 통해 필요한 객체를 요청할 때 사용할 수 있는 다양한 메서드들이 있습니다.
4.1 GetRequiredService()
등록된 객체를 가져옵니다.
- 등록되지 않은 경우 예외(Exception)가 발생합니다.
var controller = provider.GetRequiredService<MyController>();
controller.Run();
4.2 GetService()
등록된 객체를 가져오되,
등록되어 있지 않으면 null을 반환합니다.
var controller = provider.GetService<MyController>();
if (controller != null)
{
controller.Run();
}
else
{
Console.WriteLine("서비스가 등록되지 않았습니다.");
}
옵셔널 서비스가 필요한 경우 유용합니다.
4.3 CreateScope()
Scoped 생명주기를 다루려면 스코프를 만들어야 합니다.
using (var scope = provider.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
scopedService.Process();
}
- 스코프 안에서는 Scoped로 등록된 객체가 하나만 생성되고 공유됩니다.
- 스코프가 끝나면 객체들도 Dispose 됩니다.
4.4 GetServices()
복수 개의 등록된 서비스를 가져올 때 사용합니다.
var loggers = provider.GetServices<ILogger>();
foreach (var logger in loggers)
{
logger.Log("복수 로거 실행");
}
- ILogger를 구현한 여러 서비스가 등록되어 있을 때 유용합니다.
5. provider 함수 요약
메서드 | 설명 | 실패 시 처리 |
GetRequiredService() | 등록된 객체 가져오기 | 등록 안 되어 있으면 예외 |
GetService() | 등록된 객체 가져오기 | 등록 안 되어 있으면 null |
CreateScope() | 새로운 스코프 생성 | 스코프 종료 시 Dispose |
GetServices() | 여러 객체 모두 가져오기 | 빈 리스트 반환 가능 |
6. 직접 new 방식과 services 방식 비교
구분 | 직접 new 방식 | services + provider 방식 |
객체 생성 | 직접 new 사용 | 자동 생성 및 주입 |
의존성 관리 | 직접 생성자 호출 | 필요한 의존성 자동 주입 |
테스트 편의성 | Mock 주입 어려움 | Mock 객체 쉽게 주입 가능 |
유지보수성 | 코드 수정 필요 | 등록만 수정하면 됨 |
services와 provider를 제대로 활용하면
느슨한 결합(loose coupling), 유지보수성, 테스트 편의성을 모두 얻을 수 있습니다.
7. 실전에서는 어디서 보이나?
- ASP.NET Core 웹 프로젝트 (Startup.cs, Program.cs)
- WinForms, WPF, Console 앱의 Main() 메서드
- Blazor, MAUI 등 최신 .NET 프로젝트 전반
.NET 생태계 전체에서
services + provider 구조는 DI의 표준 설계 패턴입니다.
마무리
services는 의존성 등록을 담당하고,
provider는 그 등록된 규칙에 따라 객체를 생성하고 주입하는 역할을 합니다.
이 두 가지를 제대로 이해하고 활용하면
확장성, 테스트 편의성, 구조적 안정성 모두 크게 향상됩니다.
C#에서 견고한 아키텍처를 구축하려면
services와 provider를 제대로 다루는 것이 필수입니다.
해당 게시글은 ChatGPT의 도움을 받아 작성되었습니다.
'Language > C#' 카테고리의 다른 글
C# DI(Dependency Injection)와 DI 컨테이너 정리 (0) | 2025.04.27 |
---|---|
C#에서 로깅 서비스를 모듈화하는 방법 (0) | 2025.04.27 |