한 주 동안 .NET 생태계에서 있었던 주요 이슈와 아티클, 기술 트렌드를 정리해 소개합니다.
MSIX 신뢰 사슬과 패키지 무결성
주요 내용
- 실행 프로세스 → AppxManifest.xml
<Identity>→ AppxBlockMap.xml(파일 해시) → AppxSignature.p7x → 개발자 인증서로 이어지는 신뢰 사슬 역추적 - 패키지 프로세스의 아이덴티티는 토큰의
WIN://SYSAPPID속성에 있고 읽기만 가능하며 쓰기는 커널만 수행 — 런타임 변조 차단의 근거 - 설치 시 배포 엔진이 인증서 신뢰, 서명 이후 블록맵 무결성, 매니페스트 해시 일치를 검증하고 실패 시 배포 거부
- PICE는 Store 서명이 Windows 8.1부터, 매니페스트 opt-in(
<uap10:PackageIntegrity>)이 Windows 10 2004부터. pkgdir ACL 제한과 Process Trust Label ACE는icacls로 확인 가능 - 보안 모델이 커널 무결성을 전제하며, 커널이 침해되면 모든 보호가 무력화된다는 한계를 명시. 이 런타임 신뢰 모델은 USPTO 특허 US-8990561-B2로 등록
C# Channels와 백프레셔
- 저자: Shanto462
- 태그: csharp #channels #concurrency
주요 내용
ConcurrentQueue<T>에 없는 4가지(async 대기, 백프레셔, 완료 신호, 예외 전파)를System.Threading.Channels가 채우는 구조BoundedChannelOptions의FullMode4종별 적용 사례와 Drop 계열의 데이터 손실,SingleReader/SingleWriter는 런타임이 강제하지 않는 계약이라는 점,AllowSynchronousContinuations의 재진입 위험- 소비 관용구 2종 —
WaitToReadAsync+TryRead이중 루프(배칭 유리)와await foreach (ReadAllAsync) Complete()는 일회성이라 중복 호출 시 예외가 나므로TryComplete()사용,ReadAllAsync는 원본 예외를 재던지지만ReadAsync는ChannelClosedException으로 감쌈- 함정 7가지와, raw 이벤트 채널 → 변환 워커 N개 → 처리 채널 → 싱크로 이어지는 2단 인제스천 파이프라인 예제 약 150줄
C# 비동기 스트림과 IAsyncEnumerable
https://medium.com/@shanto462/mastering-async-streams-in-c-iasyncenumerable-guide-a5d2befcbecc
주요 내용
Task<IEnumerable<T>>(버퍼드)와IEnumerable<T>(동기 스트리밍)의 양자택일을 짚고,IAsyncEnumerable<T>가 첫 아이템 지연을 전체 파이프라인에서 첫 페이지로 줄이는 구조await foreach디슈가링과,[EnumeratorCancellation]을 빠뜨리면.WithCancellation()으로 넘긴 토큰이 조용히 무시되는 함정- EF Core
AsAsyncEnumerable()(열거 수명 동안 리더·커넥션 유지),JsonSerializer.DeserializeAsyncEnumerable<T>,HttpCompletionOption.ResponseHeadersRead조합 - .NET 10에서
System.Linq.AsyncEnumerable이 런타임 내장. 암시적 파라미터 async 셀렉터가 모호성 오류를 내(Order o, CancellationToken ct) =>명시가 필요하고,System.Linq.Async7.0.0 미만과 net10.0 내장 간 충돌 발생 - pull 기반 async stream과 push 기반
Channel<T>의 백프레셔 모델 차이, 예외가 호출 시점이 아닌 첫MoveNextAsync()에서 발생하는 등 함정 7가지
C# DateTimeOffset 포맷 — ISO 8601, RFC 3339, JSON, Unix Time
주요 내용
- 포맷 문자열별 보존 정보 —
O(오프셋 + 소수점 7자리 유지),s(오프셋 손실),u(UTC 변환, 밀리초 손실),R(HTTP 헤더용 UTC 전용) - RFC 3339는 ISO 8601의 좁은 프로파일이며, 별도 요구가 없으면 명시적 오프셋을 가진 RFC 3339가 전송 포맷의 기본값
- System.Text.Json 기본 직렬화는 유효한 RFC 3339를 생성하고,
DateTimeOffset은 UTC를+00:00으로DateTimeKind.Utc인DateTime은Z로 직렬화. UTC 전용 계약을 강제하는 커스텀 컨버터 전체 구현 수록 - Unix 시간의 초/밀리초 혼동은 예외가 아니라 1970년 1월 날짜로 조용히 잘못 해석됨
- UTC 변환 없이
Z만 붙여 09:11:30이 두 시간 미래로 이동하는 사례,TryParse에서 오프셋 누락 시 머신 로컬 타임존 적용, 브라우저Date가 오프셋 없는 문자열은 로컬로 날짜만 있는 문자열은 UTC로 반대 해석하는 동작
Rx.NET v7.0 릴리스
- 저자: Ian Griffiths
- 태그: #rx #reactive #packaging
주요 내용
- 세 가지 breaking change — .NET 6/7 타깃 제거(.NET 8+만 지원),
OfType연산자의 nullability 어노테이션 수정, UI 프레임워크 지원의 별도 패키지 분리 - UI 지원이
System.Reactive.Windows.Forms,System.Reactive.Wpf,System.Reactive.Uwp,System.Reactive.WindowsRuntime으로 분리 - self-contained 배포 크기 — Windows 타깃 콘솔 앱이 Rx 6.1 추가 시 102MB → 197MB였으나 Rx 7.0에서는 103MB. WPF/WinForms 의존성 강제로 발생하던 약 95MB 증가분 제거
- 런타임 바이너리 호환성은 유지 — UI 코드는 어셈블리에 남고 참조 어셈블리에서만 숨김. ReactiveUI 등 기존 라이브러리는 재컴파일 없이 동작하고, 새 코드는 애널라이저가 컴파일 오류로 적절한 패키지를 안내
- 배경으로 Rx 4.0의 “great unification” 이후 .NET Core 3.0에서 Windows 데스크톱 프레임워크가 옵션이 되며 생긴 문제를 회고
IAsyncEnumerable로 메모리 사용량 50배 줄이기
https://medium.com/@cbjpathirana/reduce-c-memory-usage-50x-iasyncenumerable-guide-73efbb012f47
- 저자: Chathuranga Pathirana
- 태그: csharp #iasyncenumerable #efcore
주요 내용
- EF Core에서
ToListAsync()로 전체를 적재하던 집계 엔드포인트를AsAsyncEnumerable()+await foreach스트리밍으로 전환 - 200k행 Postgres 기준 peak retained 메모리 35.2MB → 0.7MB, 800k행에서 121.5MB → 2.7MB. 실행 시간 77.94ms → 58.76ms
- BenchmarkDotNet의 MemoryDiagnoser가 peak 메모리를 보고하지 않아
GC.GetTotalMemory(forceFullCollection: true)로 별도 샘플링 - 스트리밍 집계는 입력이 정렬돼 있어야 성립하며(
OrderBy필수) 이는 선택이 아닌 정확성 요건 - 그룹 단위 누적 후 방출 패턴에 한정된 이득이고, 전체 결과를 한 번에 필요로 하면 해당 없음
.NET 결제 플랫폼 레퍼런스 아키텍처
- 저자: Rinat Kozin
- 태그: #payments #ledger #architecture
주요 내용
- 원장은 append-only posting — posting은 수정하지 않고 취소도 별도 posting으로 처리. 계정에 balance 필드를 두지 않고
SUM(Delta)로 계산하며 스냅샷은 버려도 되는 캐시. 금액은NUMERIC(38,18) - 한 트랜잭션에 posting·outbox row·잔액 스냅샷·멱등성 마커만 넣고 네트워크 I/O는 금지. 교착 방지를 위해 계정 잠금은 항상 id 오름차순
- 멱등성 2단 — 라우트 레벨 idempotency key로 DB 접근 전 차단, 모델 레벨에서 posting에 embed된 operation ID를 잠금 하에 TOCTOU 가드와 확인
- Outbox는 평면 SQL 테이블(
payload jsonb,processed)로 두고 200건 배치 + 500ms 폴링 후 트랜잭션 ACK - 모듈 분할 기준은 "배포 단위 = 장애 단위"로 비즈니스 도메인이 아닌 외부 프로토콜 기준. 클러스터는 Cluster→Group→Node 3계층에 epoch fencing 리더 선출
DI 컨테이너가 지금 숨기고 있는 버그
https://medium.com/@sudipparajuli/your-di-container-is-hiding-a-bug-right-now-49542f649bc2
- 저자: Sudip Parajuli
- 태그: dependencyinjection aspnetcore #servicelifetime
주요 내용
AddSingleton<ReportCache>()가AddScoped<AppDbContext>()를 주입받는 captive dependency 구조- 수명별 용도 — Transient는 무상태 헬퍼·검증기, Scoped는
DbContext와 그 의존 서비스, Singleton은 설정 객체와 스레드 안전 캐시 - 싱글턴에서 스코프 서비스가 필요하면
IServiceScopeFactory.CreateScope()후GetRequiredService<T>() - 스코프 검증이 Development 환경 밖에서는 기본 활성화되지 않아 이 버그가 트래픽이 몰릴 때까지 드러나지 않음
- 실제 대체 구현이 없는데 인터페이스만 뽑는 것은 테스트성을 개선하지 않으며, DI가 잘못된 도구인 경우도 있다는 정리
병렬 실행에서 Dictionary가 깨지는 이유
- 저자: Ravikumar Makwana
- 태그: csharp #concurrency #collections
주요 내용
Parallel.For로Dictionary<TKey,TValue>를 쓰면 내부 버킷·리사이즈 중 상태가 깨져IndexOutOfRangeException이나 무한 루프, 조용한 데이터 유실이 발생하는 메커니즘ConcurrentDictionary·ConcurrentQueue·ConcurrentStack·ConcurrentBag·BlockingCollection의 용도 구분GetOrAdd()의 팩토리 델리게이트가 경합 시 여러 번 실행될 수 있고 저장되는 결과는 하나뿐이므로, 값 생성이 비싸면ConcurrentDictionary<int, Lazy<Connection>>로 감싸는 패턴- "thread-safe"는 개별 연산 단위 보장이라
ContainsKey()후TryAdd()같은 check-then-act는 여전히 경합.AddOrUpdate()등 원자적 메서드 사용 - 단일 스레드에서는 동기화 오버헤드 때문에 일반
Dictionary가 낫고, 한 스레드가 만들고 여럿이 읽기만 하면 불변 컬렉션이 적합
AutoMapper 제거 — 컴파일 타임 DTO 매핑
- 저자: Ivan Ball-llovera
- 태그: #automapper #mapperly #sourcegenerator
주요 내용
- 리플렉션 규약 매핑의 실패 모드 —
Product.Title을Name으로 개명하면 DTO의Title매핑이 말없이 중단되는 조용한 오매핑, 프로덕션에서야 발견되는 런타임 프로파일 오류 - 읽기/쓰기 분리 —
IEntityDTOMapper는 DTO를 직접 반환하고,IEntityRequestMapper는Task<Result<TEntity>>를 반환. 요청 매핑은 곧 검증이므로 예외가 아닌 Result로 실패 표현 - Riok.Mapperly 4.3.1 소스 제너레이터가 대입 보일러플레이트를 컴파일 타임에 생성하고 미매핑 프로퍼티를 빌드 에러로 검출
- 이식 가능한 5원칙 — 읽기/쓰기 분리, 쓰기 매퍼는 result 반환, 리플렉션 대신 소스 제너레이터, 컴파일러가 누락 검출, 시작 시 스캔 후 정적 유지
- 트레이드오프로 보일러플레이트 증가, Mapperly 학습 곡선, 엔티티마다 매퍼 클래스 필요, 이득이 즉시가 아니라 "6개월 차"에 나타난다는 점을 함께 제시
가벼운 읽을거리
후보 항목 중 이슈로 선정되지 않은 가벼운 읽을거리들
10년 만의 Visual Studio 복귀와 완성 기능 충돌
- IntelliSense(언어 인식 멤버 목록), IntelliCode(로컬 트랜스포머 기반 whole-line 예측), Copilot(생성 완성과 다음 편집 위치 예측)의 세 계층이 같은 에디터에서 겹쳐 동작하는 구조와 2023년부터의 충돌 이력을 GitHub 이슈·토론 8건 이상으로 추적
- Microsoft 대응은 초기 “Tab 두 번” 통합에서 VS 2026의 IntelliSense 활성 중 Copilot 억제 후 재개하는 우선순위 분리로 변경
- 설정 위치도 VS 2022
Tools > Options > IntelliCode에서 VS 2026Text Editor > Code Completions로 이동
.NET MAUI에서 Sentry와 로컬 파일 로깅 결합
- Sentry가 자동 수집하는 디바이스·OS 정보, 앱 버전, breadcrumb을 클라우드 전송 동의와 무관하게 로컬 진단에 재사용하는 구성(
Sentry.Maui+NReco.Logging.File) SetBeforeSend()훅에서SentryEvent를 가로채 JSON으로 파일에 남기고,ISettingsService의 동의 값으로 클라우드 전송 여부 결정- 파일 로거는
FileSystem.Current.CacheDirectory에 최대 5개·각 10MB 롤링.SentryEventLogger가 자기 이벤트를 필터링하지 않으면 StackOverflowException 발생
.NET 현대화 33부 — 조심스럽게 쓴 아키텍처 패턴
- ASP.NET Framework → ASP.NET Core 마이그레이션 중 DDD·이벤트 소싱·마이크로서비스를 어디까지 도입할지 판단한 기준
- 플랫폼 이전과 아키텍처 재설계를 동시에 진행하지 않는다는 원칙 — “프레임워크 마이그레이션 + 분산 시스템 분해 = 더 많은 실패 모드와 어려운 진단”
- 이벤트는 본질적으로 비동기인 작업에만 쓰고 메서드 호출의 범용 대체물로 쓰지 않는다는 지침
하나의 SignalR 허브 위의 1초 미만 라이브 채널
- 하나의
NotificationHub에 durable용IPushNotificationSender와 ephemeral용ILiveChannelPublisher두 경로를 두고, 기본 등록은 no-op인NullLiveChannelPublisher - 채널 키를 1초 매치 타임아웃의 정규식으로 검증하고, SignalR 자동 재연결 시 그룹 멤버십이 유지되지 않아 클라이언트가 재참여해야 함
- 서비스 경계를 넘을 때는 2초 데드라인의 gRPC와 미리 직렬화된 JSON 페이로드로 공유 타입 계약 제거. 투표 커밋 후 논블로킹 인큐, 큐 포화 시 드롭
.NET에서 Rune으로 유니코드 문자 이름 얻기
Rune에서 유니코드 문자 이름(U+1F600 → “GRINNING FACE”)을 얻는 두 방법DllImport로 OS 제공 ICU의u_charName을 P/Invoke 호출하고UCharNameChoice·UErrorCode열거형을 함께 선언하거나, 관리형 패키지의GetCharacterInfo(rune).Value.Name사용- ICU는 OS 라이브러리에 의존하는 대신 앱 크기가 작고, 관리형 패키지는 이식성이 좋지만 유니코드 데이터가 함께 배포돼 앱 크기가 커짐
소스 생성 Refit 클라이언트
- Refit v14가 리플렉션 기반에서 소스 생성으로 전환. 이전에는 인터페이스가 소스 생성돼도 쿼리 문자열 처리와 타입 직렬화에 런타임 리플렉션이 남아 트리밍 경고를 피할 수 없었음
- PR #2210에서 해당 로직을 코드 생성 시점에 인라인해 런타임 리플렉션을 제거, 전체 AOT 컴파일이 트리밍 경고 없이 가능
- 마이그레이션은
RestService.For<T>→RestService.ForGenerated<T>,AddRefitClient→AddRefitGeneratedClient, 쿼리 파라미터에서[AliasAs]대신[JsonPropertyName]
프로덕션의 Hangfire — 구조·실패·상태 추적
- 프로듀서(API)와 워커를 분리해 공유 스토리지로만 통신 — API에서는
AddHangfireServer()를 호출하지 않고, 워커에서WorkerCount조정(기본 CPU × 5) [Queue]특성과 큐 이름 상수로 우선순위 라우팅.Queues배열의 순서가 곧 우선순위이며 실행 중인 잡을 선점하지 못해 용량 보장이 필요하면 전용 서버 인스턴스 필요PermanentJobException기반 클래스 +AutomaticRetryAttribute.ExceptOn글로벌 필터로 비일시적 오류를 재시도 없이 즉시 실패 처리하고,JobStorage.Current.GetMonitoringApi().JobDetails()로 DB 직접 접근 없이 잡 상태 조회
WebScene — WebView 없이 네이티브에서 웹 컴포넌트 실행
- 패키징된 React/TypeScript/JS 컴포넌트를 WebView 없이 네이티브 앱 프레임워크에서 실행하는 런타임. V8을 스크립트 엔진으로 쓰고 DOM/CSS/레이아웃 엔진을 자체 구현
- V8·DOM 상태·레이아웃·입력 디스패치를 UI 스레드 밖에서 실행하고 damage-aware diff만 네이티브 프레젠터로 발행. Avalonia가 레퍼런스 구현이고 Flutter·Uno Platform은 통합 검증 수준, WPF·WinUI는 계획
- Monaco Editor 0.56.0 번들을 무수정 실행해 텍스트 레이아웃·구문 강조·코드 폴딩 동작 확인. "임의의 공개 웹사이트를 위한 drop-in 브라우저가 아님"을 명시
