var 패턴 / discard(_) 패턴Deconstruct / recordand, or, notwhen 가드와 패턴의 조합foreach + 튜플/패턴 활용var 패턴, discard(_) 패턴패턴 매칭의 가장 기본 형태는 “타입 검사 + 캐스팅”을 한 줄로 합친 선언 패턴이다.
if (obj is string s)
{
// s는 이미 string으로 캐스팅된 상태
Console.WriteLine(s.ToUpper());
}
obj is string s의 의미
obj가 string 타입이면 trues라는 이름의 지역 변수에 캐스팅된 결과를 넣어줌예전에는
if (obj is string)
{
string s = (string)obj;
...
}
이렇게 타입 체크와 캐스팅을 따로 했지만, 선언 패턴 덕분에 한 줄로 줄일 수 있다.
var 패턴var 패턴은 “어떤 값이든 받되 이름만 붙이고 싶다”는 느낌으로 사용할 수 있다.
if (obj is var x)
{
// 항상 true, obj를 x라는 이름으로 받는 패턴
Console.WriteLine(x);
}
이렇게만 보면 굳이 쓸 이유가 별로 없는데, 다른 패턴과 섞을 때 “중간 값에 이름 붙이기” 용도로 쓸 수 있다.
if (n is > 0 and < 100 and var inRange)
{
Console.WriteLine($"범위 안 숫자: {inRange}");
}
n is > 0 and < 100으로 0 < n < 100 범위를 체크and var inRange로 그 값을 inRange라는 이름으로 꺼내 사용즉, 조건 검사 + 별칭(alias) 부여 용도로 쓰는 패턴이다.
_) 패턴
_는 “이 값은 굳이 쓰지 않을 거라서 버리겠다”는 의미의 패턴이다.
(int x, int y, int z) point = (1, 2, 3);
if (point is (_, _, 3))
{
Console.WriteLine("세 번째 값이 3이다");
}
(_, _, 3) → 첫 번째, 두 번째 값은 신경 안 쓰고, 세 번째가 3인지 여부만 확인가장 익숙한 예는 switch의 default 역할이다.
return code switch
{
200 => "OK",
400 => "Bad Request",
_ => "Unknown"
};
_는 “나머지 모든 경우”를 의미하는 패턴이며,
값을 버리는 패턴(discard)이라고 이해하면 된다.
위치(포지셔널) 패턴은 튜플이나 Deconstruct를 제공하는 타입을 “좌표”처럼 다룰 때 사용하는 패턴이다.
(int x, int y) p = (10, 0);
string Describe((int x, int y) point) => point switch
{
(0, 0) => "원점",
(_, 0) => "X축 위",
(0, _) => "Y축 위",
var (a, b) => $"일반 점 ({a}, {b})"
};
(0, 0) : x == 0, y == 0 일 때(_, 0) : y == 0이고 x는 어떤 값이든(0, _) : x == 0이고 y는 어떤 값이든var (a, b) : 나머지 모든 경우 → 튜플 요소를 a, b로 꺼냄Deconstruct를 가진 클래스/struct에도 사용 가능
튜플뿐 아니라, Deconstruct 메서드를 가진 타입도 위치 패턴으로 매칭할 수 있다.
class Range
{
public int Start { get; }
public int End { get; }
public Range(int start, int end) => (Start, End) = (start, end);
public void Deconstruct(out int start, out int end)
=> (start, end) = (Start, End);
}
string Describe(Range r) => r switch
{
(0, 0) => "빈 구간",
(0, > 0) => "0 이상 양수 구간",
var (s, e) => $"[{s} ~ {e}] 구간"
};
여기서 (0, > 0) 같은 부분은
“위치 패턴 + 관계 패턴(> 0)”이 섞인 형태다.
and, or, not
논리 패턴은 기존의 &&, ||, !와 비슷한 역할을 하지만,
패턴 문법 안에서 자연스럽게 섞어서 쓸 수 있는 버전이다.
if (n is >= 0 and <= 100)
{
// 0~100 사이
}
if (n is < 0 or > 100)
{
// 범위 밖
}
if (obj is not null)
{
// null 아님
}
예를 들어 ERP 느낌으로 상태와 금액을 같이 볼 수도 있다.
if (order is { Status: "CLOSE" or "CANCEL", Amount: > 0 })
{
Console.WriteLine("종료/취소 상태지만 금액은 0보다 큼");
}
Status: "CLOSE" or "CANCEL" → 두 상태 중 하나면 매칭Amount: > 0 → 금액이 0보다 큰 경우만이렇게 패턴만으로도 꽤 복잡한 조건을 표현할 수 있다.
C# 11부터 추가된 리스트 패턴은 배열/리스트의 형태로 매칭하는 기능이다.
int[] arr = { 1, 2, 3, 4 };
string Describe(int[] xs) => xs switch
{
[] => "빈 배열",
[0] => "0 하나만",
[1, 2, 3] => "1,2,3 딱 세 개",
[1, ..] => "1로 시작",
[.., 4] => "4로 끝",
[_, _, _, _] => "길이 4",
[1, .. var rest] => $"1로 시작 + 나머지 {rest.Length}개",
_ => "그 외"
};
[] → 빈 배열[0] → 요소가 하나이고 그 값이 0[1, 2, 3] → 정확히 1,2,3 세 개일 때[1, ..] → 첫 번째가 1이고 뒤에는 뭐든[.., 4] → 마지막이 4[1, .. var rest] → 1로 시작하고, 나머지를 rest로 받아서 사용ERP 예를 살짝 섞어 보면:
// 특정 일자의 입출고 기록이 [입고, 출고, 출고] 순인지 체크
string CheckIoPattern(string[] iogbns) => iogbns switch
{
["I", "O", "O"] => "입고 후 2번 출고 패턴",
["I", .., "O"] => "입고로 시작해서 출고로 끝나는 패턴",
["O", ..] => "출고로 시작하는 특이 패턴",
_ => "일반 패턴"
};
리스트 패턴을 한 번 익혀두면 시간 순서, 상태 변경 패턴 같은 것도 꽤 직관적으로 표현할 수 있다.
C# 패턴의 재미는 여러 패턴들을 섞어서 쓸 수 있다는 점이다.
속성 패턴, 확장 속성 패턴, 관계 패턴, 논리 패턴 등을 한 줄에 모아서
“조건 스펙”을 딱 표처럼 표현할 수 있다.
class Order
{
public string Status { get; set; }
public decimal Amount { get; set; }
public Customer Customer { get; set; }
}
class Customer
{
public string Grade { get; set; } // A, B, C 등급
}
string Describe(Order o) => o switch
{
{ Status: "CLOSE", Customer.Grade: "A", Amount: > 1_000_000m }
=> "고액 프리미엄 종료 주문",
{ Status: "OPEN", Customer.Grade: "A" or "B" }
=> "우수 고객 진행 중 주문",
{ Status: "OPEN", Amount: <= 0 }
=> "금액 없는 진행 주문",
null
=> "null 주문",
_
=> "일반 주문"
};
예를 들어 첫 번째 패턴:
{ Status: "CLOSE", Customer.Grade: "A", Amount: > 1_000_000m }
Status, AmountCustomer.GradeAmount: > 1_000_000m
기존에 if/else로 길게 늘어졌던 조건문을
switch + 패턴 조합으로 정리하면
“조건 정의서”를 코드로 옮겨 놓은 것처럼 깔끔하게 보이는 효과가 있다.
when 가드
when 가드는 패턴 뒤에 “추가 필터”를 붙이고 싶을 때 사용한다.
string Describe(int n) => n switch
{
var x when x < 0 => "음수",
var x when x == 0 => "0",
var x when x % 2 == 0 => "양수 짝수",
_ => "양수 홀수"
};
패턴으로 대략적인 범위를 잡고,
세부 조건은 when으로 필터링할 수도 있다.
string Describe(Order o) => o switch
{
{ Status: "CLOSE" } when o.Amount == 0
=> "금액 0인 종료 주문",
{ Status: "CLOSE" }
=> "일반 종료 주문",
_
=> "기타"
};
이런 식으로 “같은 패턴” 안에서 금액이 0인 경우와 아닌 경우를
when 가드로 나눠서 표현할 수 있다.
foreach와 튜플/패턴
foreach에서도 튜플 분해와 패턴을 같이 사용할 수 있다.
var users = new List<(string Name, int Age)>
{
("A", 10),
("B", 20),
("C", 30)
};
foreach (var (name, age) in users)
{
Console.WriteLine($"{name} / {age}");
}
각 튜플 요소를 name, age로 바로 꺼내 쓸 수 있어
user.Name, user.Age보다 더 가볍게 데이터를 다룰 수 있다.
foreach (var (name, age) in users)
{
var grade = age switch
{
< 13 => "어린이",
< 20 => "청소년",
_ => "성인"
};
Console.WriteLine($"{name}는 {grade}");
}
튜플로 받은 데이터를 바로 switch 식 + 패턴으로 분류해서
코드를 간단하고 읽기 좋게 구성할 수 있다.
C# 패턴 매칭을 머릿속에 이렇게 정리해 두면 편하다.
if (x is 패턴)switch (x) { case 패턴: ... }x switch { 패턴 => 결과, ... } (switch 식)is string svar 패턴: is var x_is 0, case "CLOSE":<, >, <=, >=and, or, not{ Prop: ..., Other: ... }{ A.B: ..., A.C: ... }(x, y), Deconstruct[], [1, ..], [.., 4], [1, .. var rest]when 가드: 패턴 뒤에 추가 조건