복잡한 분기 로직을 if-else로만 유지하면 가독성과 유지보수가 급격히 떨어집니다. C#의 스위치 표현식과 패턴 매칭을 중첩해 사용하면 상태 조합을 선언적으로 표현할 수 있습니다. 아래 예제로 실무 도메인 로직을 간결하게 리팩터링하는 방법을 정리합니다.
1. 중첩 속성 패턴으로 주소/회원 등급 라우팅
속성 패턴과 논리 패턴(and/or)을 중첩하면 깊은 도메인 객체에서도 안전하게 매칭합니다. null에 대해 예외 없이 패턴이 실패하도록 동작하므로 안심하고 체이닝할 수 있습니다.
using System;
public record Address(string City, string Country);
public record Customer(string Tier, Address? Address);
public record Payment(string Method, bool Verified);
public enum OrderStatus { New, Packed, Shipped, Cancelled }
public record Order(OrderStatus Status, Customer? Customer, Payment? Payment, decimal Amount);
static string GetShippingBucket(Order order) => order switch
{
// KR 서울로 배송, 출고 완료
{ Status: OrderStatus.Shipped, Customer: { Address: { Country: "KR", City: "Seoul" } } } => "KR-SEOUL-EXPRESS",
// KR 고가 주문
{ Status: OrderStatus.Shipped, Customer: { Address: { Country: "KR" } }, Amount: >= 100_000m } => "KR-HEAVY",
// KR 일반
{ Status: OrderStatus.Shipped, Customer: { Address: { Country: "KR" } } } => "KR-STANDARD",
// VIP + 미국, 출고 전 우선 처리 (and, or 조합)
{ Status: OrderStatus.Packed or OrderStatus.New, Customer: { Tier: "VIP" } and { Address: { Country: "US" } } } => "PRIORITY-US",
{ Status: OrderStatus.Cancelled } => "CANCELLED",
_ => "PENDING"
};
var o1 = new Order(OrderStatus.Shipped, new Customer("VIP", new Address("Seoul", "KR")), new Payment("Card", true), 120_000m);
var o2 = new Order(OrderStatus.New, new Customer("VIP", new Address("NYC", "US")), null, 50_000m);
Console.WriteLine(GetShippingBucket(o1)); // KR-SEOUL-EXPRESS
Console.WriteLine(GetShippingBucket(o2)); // PRIORITY-US특정 - 일반 순서로 패턴을 배치하면 겹치는 매칭을 안전하게 제어할 수 있습니다.
2. 튜플 + 논리 패턴으로 상태 조합 간단화
여러 상태를 동시에 보려면 튜플 패턴이 깔끔합니다. 일부만 비교하고 나머지는 와일드카드(_)로 흡수하면 규칙이 또렷해집니다.
static string GetNextAction(Order order) => (order.Status, order.Payment) switch
{
(OrderStatus.New, { Method: "Card", Verified: true }) => "Capture",
(OrderStatus.New, { Method: "Card", Verified: false }) => "3DS",
(OrderStatus.Packed, { Method: "Wire" }) => "WaitWire",
(OrderStatus.Shipped, _) => "Notify",
(_, null) => "NoPayment",
_ => "Review"
};
// 중첩 스위치로 라벨링 세분화
static string Label(Order o) => o.Status switch
{
OrderStatus.New => o.Customer switch
{
{ Tier: "VIP" } => "NEW-VIP",
{ Address: { Country: "KR" } } => "NEW-KR",
_ => "NEW"
},
OrderStatus.Shipped => o.Payment switch
{
{ Method: "Card", Verified: true } => "SHIPPED-PAID",
_ => "SHIPPED"
},
_ => "OTHER"
};스위치를 중첩하되 한 단계에서 하나의 책임만 다루면 가독성을 유지할 수 있습니다.
3. when 가드와 계산식, not 패턴 활용
패턴만으로 표현하기 어려운 계산식은 when 가드를 섞습니다. 또한 not, and, or로 불필요한 가드를 줄일 수 있습니다.
static bool IsHoliday() => DateTime.UtcNow.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
static decimal CalcDiscount(Order o) => o switch
{
{ Customer: { Tier: "VIP" }, Amount: >= 200_000m } => 0.15m,
{ Customer: { Tier: "VIP" } } => 0.10m,
{ Customer: not null } when IsHoliday() => 0.05m,
_ => 0m
};not 패턴으로 null 검사를 명시화하면 when 가드를 줄이고 의도를 드러낼 수 있습니다.
4. 리스트 패턴으로 시퀀스 형태 조건 매칭
C# 11의 리스트 패턴으로 이벤트 시퀀스 같은 규칙도 간결하게 작성합니다. 일부만 맞추고 나머지는 슬라이스 연산자(..)로 건너뜁니다.
public record Event(string Kind, decimal Amount);
static string Audit(Event[] events) => events switch
{
[{ Kind: "Refund" }, .., { Kind: "Refund" }] => "Multiple refunds",
[{ Kind: "Charge" }, { Kind: "Charge" }, ..] => "Double charge",
[] => "No activity",
[_, ..] => "Has activity"
};5. 실무 적용 팁
첫째, 가장 구체적인 패턴을 위에, 일반적인 패턴을 아래에 배치합니다. 둘째, 넓게 겹치는 조건은 and/or/not로 묶어 중복을 줄입니다. 셋째, 상태 조합은 튜플 패턴으로, 객체 깊이는 속성 패턴으로, 계산식은 when 가드로 역할을 분리합니다. 넷째, 스위치 표현식은 누락된 경우 컴파일 타임에 경고/에러로 잡히므로 기본(_) 케이스로 안정망을 깔아둡니다. 다섯째, 너무 깊어지면 보조 메서드로 추출해 한 화면에 1~2단계 중첩만 유지합니다.
중첩 스위치 패턴은 조건의 의도를 코드에 그대로 기록합니다. 작은 규칙부터 적용해 분기 로직을 단계적으로 정리해 보시기 바랍니다.
'C#' 카테고리의 다른 글
| C# HTTP 클라이언트 팩토리(HttpClientFactory) 사용과 베스트 프랙티스 (1) | 2026.05.13 |
|---|---|
| C# 타입 변환 연산자와 Custom TypeConverter 정의하기 (0) | 2026.05.13 |
| C# 값 튜플(ValueTuple) vs Tuple 성능 비교 (0) | 2026.05.12 |
| C# Immutable Collections 사용과 장점 (0) | 2026.05.11 |
| C# Expression Tree로 동적 코드 생성하기 (0) | 2026.05.11 |