## 1. IServiceCollection이란 무엇인가요?
IServiceCollection은 .NET 8 프레임워크가 제공하는 '애플리케이션 부품(서비스) 등록 장부'입니다.
디자인 패턴에서는 이를 IoC (Inversion of Control, 제어의 역전) 컨테이너 또는 DI (Dependency Injection, 의존성 주입) 컨테이너라고 부릅니다.
- 레거시 방식 (C++/Qt/WinForm 스타일): 통신 클래스나 암호화 클래스가 필요하면 개발자가 코드 곳곳에서 new BLEService()나 싱글톤 포인터 BLEService::GetInstance()를 직접 호출해 객체를 만들었습니다. 이는 모듈 간 결합도를 극도로 높여 장비 시뮬레이터를 붙이거나 모바일로 이식할 때 코드를 다 갈아엎어야 하는 주원인이 됩니다.
- IServiceCollection 방식: 프로그램이 시작될 때(HostApplicationBuilder 단계) "앞으로 우리 프로그램은 이 통신 클래스와 이 암호화 클래스를 사용할 거야"라고 IServiceCollection 장부에 먼저 등록해 둡니다. 그러면 프레임워크가 객체의 생명주기를 알아서 관리하며 필요한 곳에 자동으로 꽂아줍니다.
## 2. IServiceCollection 등록 메서드 종합 가이드
| 등록 메서드 종류 | 생명주기 (Lifetime) |
설명 및 특징 | 통신 시스템 활용 예시 |
| AddSingleton<T> | Single (앱 실행 동안 단 하나) |
프로그램이 켜져서 꺼질 때까지 단 하나의 메모리 인스턴스만 공유합니다. | • BleDeviceWatcherService (무선 통신 연결 및 감시자) • 전역 암호화 엔진 (AesCryptoProvider) |
| AddTransient<T> | Transient (요청 시마다 생성) |
부품을 요청할 때마다 매번 new로 새로 만들고 가비지 컬렉터(GC)가 수거합니다. | • 장치 세팅 화면 UI/ViewModel • 일회성 패킷 분석 및 직렬화 컴포넌트 |
| AddScoped<T> | Scoped (범위/세션 단위) |
하나의 요청 세션(Scope) 내에서만 동일 인스턴스를 보장합니다. | • 클라이언트가 접속하여 유지되는 통신 세션별 처리 컨텍스트 |
| AddHostedService<T> | Singleton 기반 백그라운드 | 앱 시작 시 자동으로 실행되어 background 스레드로 동작하는 데몬 서비스입니다. | • ExecuteAsync 루프를 돌며 장치의 이벤트 로그를 주기적으로 폴링하는 통신 서비스 |
| AddHttpClient<T> | Transient + 하위 소켓 풀링 | 외부 Web API나 소켓 통신을 효율적으로 처리하기 위해 프레임워크가 내부 커넥션 풀을 관리해 줍니다. | • 상위 시스템이나 웹 서버로 계측 데이터를 REST API로 전송하는 HTTP 클라이언트 모듈 |
| TryAddSingleton<T> TryAddTransient<T> |
조건부 등록 (동일 생명주기) |
장부에 이미 동일한 이름/인터페이스의 서비스가 등록되어 있다면 무시하고, 없을 때만 등록합니다. | • 공통 클래스 라이브러리(DLL) 배포 시, 개발자가 실수로 중복 등록하여 의존성 충돌이 나는 것을 방지하는 방어적 코드 작성 시 활용 |
| TryAddEnumerable<T> | 다중 구현체 등록 (동일 생명주기) |
동일한 인터페이스를 상속받은 여러 개의 서로 다른 클래스를 리스트 형태로 장부에 동시 등록합니다. | • IProtectionRule 인터페이스 아래에 규칙들을 다중 등록하여 특정 알고리즘 적용 시 |
## 3. 코드로 보는 실제 작동 원리
IServiceCollection에 등록된 부품들이 어떻게 생성자 주입(Constructor Injection)을 통해 자동으로 조립되는지 흐름을 보겠습니다.
// 1. 장부(IServiceCollection)에 서비스와 UI 등록
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IBLECommunication, EntecBleService>(); // 싱글톤 등록
builder.Services.AddTransient<CmrSettingViewModel>(); // Transient 등록
var host = builder.Build();
이제 뷰모델(ViewModel) 클래스를 만들 때, 생성자에 통신 인터페이스를 적어주기만 하면 프레임워크가 장부(IServiceCollection)를 뒤져서 알아서 객체를 넘겨줍니다. new 키워드가 사라집니다.
public class CmrSettingViewModel
{
private readonly IBLECommunication _bleService;
// 생성자 주입: .NET 8 프레임워크가 장부를 보고 EntecBleService 인스턴스를 자동으로 넘겨줌
public CmrSettingViewModel(IBLECommunication bleService)
{
_bleService = bleService;
}
public async Task UploadSettingAsync()
{
// 장부에서 주입받은 통신 모듈을 안전하게 사용
await _bleService.SendPacketAsync(new byte[] { 0x01, 0x02 });
}
}
ServiceCollectionExtensions는 .NET 8 개발에서 의존성 주입(DI) 코드를 깔끔하고 모듈화된 구조로 유지하기 위해 사용하는 핵심 개발 패턴(관례)입니다.
MFC 개발자분들이 쉽게 이해하실 수 있도록 비유하자면, 여러 모듈의 초기화 기능들을 파일별로 깔끔하게 쪼개어 정리하는 '모듈별 전용 초기화 헤더/소프트웨어 컴포넌트 패키지'를 만드는 방법이라고 생각하시면 됩니다.
## 4. 이 클래스가 왜 필요한가요? (배경)
프로젝트 규모가 커지면 HostApplicationBuilder를 사용하는 App.xaml.cs나 Program.cs 파일 안에 수십, 수백 개의 서비스(BLE 통신, 데이터베이스, 암호화 알고리즘 등)를 등록하게 됩니다.
// 레거시/나쁜 예: App.xaml.cs 한 곳에 모든 등록 코드가 몰려 지저분해짐
builder.Services.AddSingleton<IBluetoothService, BluetoothService>();
builder.Services.AddSingleton<IAesEncryptionService, AesEncryptionService>();
builder.Services.AddTransient<CalculatorViewModel>();
builder.Services.AddTransient<SubstationViewModel>();
// ... 밑으로 100줄 이상 늘어남 ...
이 상태가 되면 코드가 읽기 어려워지고 유지보수가 불가능해집니다. 이를 방지하기 위해 기능별로 등록 코드를 쪼개서 분리하는 단위가 바로 ServiceCollectionExtensions 확장 메서드 클래스입니다.
## 5. MFC 개념과의 비교 (이해 돕기)
- MFC 방식: 통신 관련 모듈들을 초기화할 때 InitCommunication(), UI 관련 초기화를 할 때 InitUILayout() 같은 전역/멤버 함수를 따로 만들어 내부에서 수동 객체 생성을 처리하곤 했습니다.
- .NET 8 방식: IServiceCollection이라는 DI 인터페이스에 확장 메서드(Extension Method)를 붙여서 builder.Services.AddCommunicationServices() 형태로 세련되게 호출합니다.
## 6. 구현 샘플 코드
통신(BLE) 모듈 영역과 비즈니스 로직(계산기) 영역을 깔끔하게 분리하는 확장 클래스를 구현해 보겠습니다.
### 1) 확장 클래스 정의 (분리된 파일들)
C#의 static class와 this 키워드를 활용하여 기존 프레임워크 인터페이스에 새로운 메서드를 주입합니다.
using Microsoft.Extensions.DependencyInjection;
namespace Substation.DependencyInjection;
// 1. 통신 관련 서비스만 모아놓은 확장 클래스
public static class CommunicationServiceExtensions
{
public static IServiceCollection AddCommunicationServices(this IServiceCollection services)
{
[cite_start]// 암호화 및 무선 통신 인프라 등록 [cite: 31, 295, 296]
services.AddSingleton<IAesCryptoProvider, AesCryptoProvider>();
services.AddSingleton<IBluetoothService, BluetoothService>();
return services; // 체이닝(연속 호출)이 가능하도록 services를 반환하는 것이 관례입니다.
}
}
// 2. 비즈니스 로직 및 UI(ViewModel) 관련 서비스만 모아놓은 확장 클래스
public static class BusinessLogicServiceExtensions
{
public static IServiceCollection AddCalculatorBusinessServices(this IServiceCollection services)
{
services.AddTransient<ICalculatorEngine, CalculatorEngine>();
services.AddTransient<MainViewModel>();
return services;
}
}
### 2) 메인 진입점에서의 활용 (App.xaml.cs 또는 Program.cs)
위와 같이 분리해 두면 메인 함수 내부가 서랍장 정리하듯 매우 명확하고 깔끔해집니다.
using Microsoft.Extensions.Hosting;
using Substation.DependencyInjection; // 확장 메서 네임스페이스 로드
var builder = Host.CreateApplicationBuilder(args);
// 프레임워크 기본 메서드처럼 당당하게 마침표(.)를 찍고 호출할 수 있습니다.
builder.Services
.AddCommunicationServices() // 통신 모듈 그룹 등록
.AddCalculatorBusinessServices(); // 계산 로직 그룹 등록
var host = builder.Build();
await host.RunAsync();
'.NET' 카테고리의 다른 글
| ResiliencePipeline 클래스란 무엇인지? (0) | 2026.06.16 |
|---|---|
| C# 숫자 리터럴 중간에 들어간 언더바(_) 사용 (0) | 2026.06.16 |
| MFC 개발자 대상 .NET 8 기반 IPC 샘플 코드 기반 교육 (0) | 2026.06.16 |
| .NET 8 개발 전환을 위한 핵심 개발자 패러다임 시프트 교육 로드맵 (0) | 2026.06.16 |
| .NET 8의 HostApplicationBuilder? (0) | 2026.06.16 |