Quick Syntax
and/or/not은 C# 9 logical pattern, Customer.IsPremium 같은 확장 property pattern은 C# 10, list/slice pattern은 C# 11부터 쓸 수 있습니다. list pattern은 아무 IEnumerable<T>가 아니라 길이(Length/Count)와 요소 접근을 제공하는 대상에만 적용됩니다.
// and / or / not 조합
string category = score switch
{
>= 90 and <= 100 => "S",
>= 70 and < 90 => "A",
>= 50 and < 70 => "B",
< 0 or > 100 => throw new ArgumentOutOfRangeException(),
_ => "F"
};
// list pattern (C# 11+)
string describe = items switch
{
[] => "비어있음",
[var single] => $"하나: {single}",
[var first, ..] => $"첫 번째: {first}, 이후 더 있음"
};문법
and / or / not — 패턴 조합
and, or, not으로 여러 패턴을 조합합니다. 산술 연산자처럼 쓰이지만 패턴 연산자입니다.
결합 우선순위는 not → and → or입니다. 복잡한 범위는 괄호로 묶습니다. 같은 결합 단계의 subpattern 검사 순서는 소스 작성 순서로 보장되지 않으므로 getter 호출이나 상태 변경 같은 부작용에 의존하지 않습니다.
// and — 두 조건 모두 만족
bool isValidAge = age is >= 0 and <= 150;
// or — 하나라도 만족
bool isWeekend = day is DayOfWeek.Saturday or DayOfWeek.Sunday;
// not — 조건 반전
bool isNotNull = value is not null;
bool isNotEmpty = items is not [];
// 괄호가 없으면 not이 and보다 먼저 결합한다.
char c = 'A';
bool isNotLower = c is not (>= 'a' and <= 'z');
// 복합 조합
string label = (x, y) switch
{
( > 0, > 0) => "1사분면",
( < 0, > 0) => "2사분면",
( < 0, < 0) => "3사분면",
( > 0, < 0) => "4사분면",
_ => "축 위"
};property pattern — 중첩 객체 분기
{ Property: pattern } 형태로 객체의 속성을 패턴으로 검사합니다. 중첩 객체도 .으로 접근해 한 번에 처리할 수 있습니다. null 입력이나 중간 property가 null이면 해당 property pattern은 매치하지 않으므로, null을 별도 처리해야 하는 switch에는 null arm을 둡니다.
// 단순 property pattern
string describe = user switch
{
{ IsAdmin: true, Name: not null } => $"관리자: {user.Name}",
{ IsActive: false } => "비활성 사용자",
_ => "일반 사용자"
};
// 중첩 property pattern
decimal discount = order switch
{
{ Customer.IsPremium: true, Total: > 100_000 } => order.Total * 0.15m,
{ Customer.IsPremium: true } => order.Total * 0.10m,
{ Total: > 50_000 } => order.Total * 0.05m,
_ => 0m
};positional pattern — 분해 가능한 타입
Deconstruct를 구현하는 타입이나 튜플에서 위치 기반으로 패턴을 적용합니다.
// 튜플 positional pattern
string quadrant = (x, y) switch
{
(0, 0) => "원점",
( > 0, > 0) => "1사분면",
( <= 0, > 0) => "2사분면",
_ => "기타"
};
// record positional pattern (record는 Deconstruct 자동 생성)
record Point(int X, int Y);
string classify = point switch
{
Point(0, 0) => "원점",
Point(var x, 0) => $"X축: {x}",
Point(0, var y) => $"Y축: {y}",
Point(var x, var y) when x == y => "대각선",
_ => "일반 위치"
};list pattern (C# 11+) — 시퀀스 구조 분기
list pattern은 배열이나 리스트의 구조(길이, 특정 위치의 값)를 기준으로 분기합니다. 대상은 Length 또는 Count와 int 인덱서 같은 list pattern shape를 제공해야 합니다. 단순히 IEnumerable<T>를 구현했다는 사실만으로는 충분하지 않습니다.
int[] scores = { 90, 85, 70 };
string result = scores switch
{
[] => "점수 없음",
[var only] => $"한 명: {only}",
[var first, var last] => $"두 명: {first}, {last}",
[var first, .., var last] => $"다수 — 처음: {first}, 마지막: {last}",
};
// 명령 파싱에서 유용
string[] args = { "move", "10", "20" };
string cmd = args switch
{
["move", var x, var y] => $"이동: ({x}, {y})",
["shoot", var target] => $"사격: {target}",
["quit"] => "종료",
_ => "알 수 없는 명령"
};..은 슬라이스 패턴으로 나머지 요소들과 매칭하며 한 list pattern에는 하나만 둘 수 있습니다. .. var rest처럼 나머지를 캡처하려면 대상이 range/slice 결과를 제공해야 하고, 그 결과가 복사인지 view인지는 타입마다 다릅니다.
패턴 선택
| 패턴 | 예시 | 잘 맞는 상황 |
|---|---|---|
and / or / not | >= 0 and < 100 | 범위 조합, 복합 조건 |
| property pattern | { Name: not null } | 객체 속성 기반 분기 |
| 중첩 property pattern | { Order.Total: > 0 } | 깊은 구조 한 번에 분기 |
| positional pattern | (x, y) / Point(var x, _) | 튜플, record 분해 |
| list pattern | [first, .., last] | 시퀀스 구조 분기, 파서 |
주의할 점
패턴 하나에 규칙을 너무 많이 밀어 넣으면 짧아 보여도 해석이 어렵습니다. and/or가 3개 이상 중첩되거나 property pattern이 깊어지면 보조 메서드로 분리하는 편이 좋습니다.
switch expression의 arm 순서가 중요합니다. 구체적인 arm 뒤에 그 arm이 이미 모두 덮는 넓은 arm을 둘 수는 없으며, 컴파일러는 unreachable arm을 오류(CS8510)로 보고합니다. 가능한 값이 모두 처리되지 않은 switch expression은 경고(CS8509)를 내거나 실행 시 SwitchExpressionException이 될 수 있으므로, _ fallback 또는 명시적 throw 정책을 정합니다.
참고 링크
1 sources