패턴 매칭은 값을 어떤 모양에 대해 검사합니다. "Circle인가?", "null인가?", "Status가 "paid"이고 Total이 100을 넘는가?" 같은 것입니다. 검사가 성공하면 패턴은 값의 일부를 새 변수로 꺼낼 수도 있습니다. C# 7에서 is 연산자로 작게 시작해 그 뒤 모든 버전에서 커졌으므로, 이 페이지는 각 형태의 버전을 함께 적습니다.
is 타입 패턴(C# 7)
C# 7 이전에는 타입을 검사하고 쓰는 데 두 단계가 걸렸습니다. as와 null 검사, 또는 is와 캐스트입니다. 타입 패턴은 둘을 한 번에 하고 타입이 지정된 변수를 줍니다:
출력:
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card는 p가 null이 아닌 CardPayment(또는 그로부터 파생된 클래스)일 때만 참이며, 그때 card가 올바른 정적 타입으로 그것을 담습니다. null 값은 타입 패턴과 절대 일치하지 않으므로, null 결제는 null 검사까지 내려갑니다.
범위와 확정 할당
패턴 변수는 둘러싼 블록에 존재하지만, 컴파일러는 일치가 성공했음이 알려진 곳에서만 읽도록 허용합니다. 그래서 일찍 빠져나가고 그 뒤에 변수를 쓰는 "보호 절" 형태가 동작합니다:
출력:
19.99
42 is over 40
price must be text
boxed is int n && n > 40은 또 다른 흔한 형태를 보여 줍니다. &&의 오른쪽은 일치가 성공했을 때만 실행되므로 거기서 변수를 쓸 수 있습니다. ||의 오른쪽에서는 할당되지 않으며, 컴파일러가 그렇다고 알려 줍니다(CS0165).
패턴은 언박싱도 합니다. boxed is int n은 박싱된 int에 대해 성공하고, 예외를 던질 수 있는 캐스트 없이 값을 줍니다. nullable 값 형식에서 maybe is int n은 maybe에 값이 있을 때 정확히 성공합니다.
상수 패턴과 null 패턴
상수도 패턴입니다. x is null이 가장 많이 쓰이며, C# 7은 x is 0, status is "paid", 열거형 멤버도 허용합니다:
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
타입의 연산자 오버로드에 의해 방향이 바뀔 수 없으므로 많은 팀이 == null보다 is null을 선호합니다. C# 9는 부정 형태 is not null을 추가했습니다.
switch 문의 패턴(C# 7)
같은 타입 패턴을 case 레이블로 쓸 수 있고, when 절이 조건을 더합니다. case는 위에서 아래로 시도되므로 구체적인 것부터 일반적인 것 순으로 두세요:
static string Describe(object o)
{
switch (o)
{
case null: return "nothing";
case int n when n < 0: return "negative number";
case int n: return $"number {n}";
case string s when s.Length == 0: return "empty text";
case string s: return $"text of {s.Length} chars";
case IEnumerable<int> list: return "a sequence of numbers";
default: return o.GetType().Name;
}
}
Describe(-4); // negative number
Describe(""); // empty text
Describe(new List<int>()); // a sequence of numbers
Describe(2.5); // Double
컴파일러가 순서를 검사합니다. 앞의 case가 덮어서 절대 일치할 수 없는 case는 CS8120 오류입니다. default는 어디에 있든 항상 마지막에 평가됩니다. 위의 결제 예제처럼 if/is 체인으로 같은 로직을 쓰면 C# 7 이후 모든 버전에서 동작합니다.
switch 식(C# 8)
switch 식은 "여러 경우에서 값 하나를 계산하기"를 위한 간결한 형태입니다. 값이 먼저, 그다음 switch, 그다음 쉼표로 구분한 pattern => result 갈래가 오며, _가 나머지 모두를 받습니다:
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new ArgumentException("Unknown shape", nameof(shape)),
};
Area(new Rectangle(3, 4)); // 12
실행 시점에 어떤 갈래도 일치하지 않으면 SwitchExpressionException을 던지고, 처리되지 않는 입력이 보이면 컴파일러가 경고(CS8509)합니다. 문법은 switch 식 페이지에서 따로 다루며, 이 페이지의 나머지는 갈래에 넣을 수 있는 패턴에 관한 것입니다.
속성 패턴(C# 8, C# 10에서 확장)
속성 패턴은 중괄호 안에서 객체의 속성을 중첩 패턴과 대조합니다. 객체는 null이 아니어야 하므로 { }만 쓰면 "null이 아님"을 뜻합니다:
public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);
static string Shipping(Order order) => order switch
{
{ Status: "cancelled" } => "no shipment",
{ ShipTo.Country: "PT", Total: >= 50m } => "free, domestic", // C# 10 dotted form
{ ShipTo.Country: "PT" } => "4.90, domestic",
{ Total: > 200m } => "free, international",
_ => "12.00, international",
};
Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2)); // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2)); // free, international
C# 8과 9에서는 중첩 형태가 필요합니다: { ShipTo: { Country: "PT" } }. C# 10은 위에서 쓴 점 표기 축약형을 허용합니다. 속성 패턴은 is와도 동작하므로 여러 조건의 검사가 설명처럼 읽힙니다: if (order is { Status: "paid", Items: > 0 }).
관계 패턴과 논리 패턴(C# 9)
C# 9는 비교(<, <=, >, >=)와 조합자 and, or, not을 추가해 범위를 패턴으로 만들었습니다:
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 75 and < 90 => "B",
>= 50 => "C",
_ => "F",
};
Grade(82); // B
Grade(101); // invalid
if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // and binds tighter than or
C# 7에서의 대응은 if/else if 체인이나 when 절을 쓰는 switch(case int s when s >= 90:)입니다.
튜플 패턴과 위치 패턴(C# 8)
여러 값으로 한 번에 switch하려면 튜플로 switch합니다:
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(> 0, > 0) => "I",
(< 0, > 0) => "II",
(< 0, < 0) => "III",
(> 0, < 0) => "IV",
_ => "on an axis",
};
Quadrant(-2, 5); // II
위치 패턴은 레코드를 포함해 Deconstruct 메서드를 가진 모든 타입에 같은 일을 합니다: case Point(0, 0):이나 p is Rectangle(var w, var h) && w == h.
목록 패턴(C# 11)
목록 패턴은 배열과 리스트를 요소로 대조합니다. ..는 요소 몇 개든("슬라이스") 일치하고, 각 요소 위치에는 다른 어떤 패턴이든 올 수 있습니다:
static string Route(string[] args) => args switch
{
[] => "help",
["add", var item] => $"add {item}",
["remove", var item, ..] => $"remove {item}",
[var cmd, ..] => $"unknown command {cmd}",
};
Route(new[] { "add", "milk" }); // add milk
Route(new[] { "remove", "milk", "now" }); // remove milk
int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last); // 4
어느 버전이 무엇을 추가했나
| 패턴 | 예 | 버전 |
|---|---|---|
| 변수가 있는 타입 패턴 | o is Circle c | C# 7.0 |
| 상수 패턴 | x is null, n is 0 | C# 7.0 |
var 패턴 | x is var v | C# 7.0 |
case 레이블의 패턴, when 가드 | case int n when n < 0: | C# 7.0 |
switch 식, 무시 항목 _ | x switch { ... } | C# 8 |
| 속성 패턴 | { Status: "paid" } | C# 8 |
| 튜플 패턴 | (x, y) switch { (0, 0) => ... } | C# 8 |
| 위치 패턴 | Point(0, 0) | C# 8 |
| 관계 패턴 | > 100, <= 0 | C# 9 |
| 논리 패턴 | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| 단독 타입 패턴 | case Circle:, Circle => ... | C# 9 |
| 확장 속성 패턴 | { ShipTo.City: "Porto" } | C# 10 |
| 목록 패턴과 슬라이스 | [first, .., last] | C# 11 |
흔한 실수
- case를 일반적인 것부터 구체적인 것 순으로 두기.
case CardPayment c:앞의case Payment p:는 두 번째를 도달할 수 없게 만듭니다(CS8120). 구체적인 것을 먼저 두세요. - 타입 패턴이 null과 일치하리라 기대하기.
null is string s는 거짓입니다.null은 별도의 case나is null로 처리하세요. - 일치가 실패했을 수 있는 곳에서 패턴 변수 읽기.
a is int n || ...뒤에서n은 할당되지 않습니다(CS0165). - 가상 메서드 대신 긴 타입 switch. 새 하위 클래스마다 다섯 곳의 같은 switch를 고쳐야 한다면 그 동작은 클래스 계층에 있어야 합니다. 패턴은 외부에서 온 데이터(JSON 모양, 메시지, 입력의 튜플)와 직접 통제하는 닫힌 타입 집합에서 빛납니다.
- switch 식에서 나머지 모두를 받는 갈래를 잊기. 일치하지 않는 입력은 실행 시점에 예외를 던집니다. 경고 CS8509를 읽으세요.
자주 묻는 질문
C#에서 패턴 매칭이란 무엇인가요?
패턴 매칭은 값을 어떤 모양에 대해 검사하고, 일치하면 같은 단계에서 값의 일부를 변수로 꺼낼 수 있습니다. if (shape is Circle c)는 타입을 검사하고 타입이 지정된 c를 주며, order is { Status: "paid", Total: > 100 }은 속성을 검사합니다. 패턴은 is 식, switch 문, switch 식에 나타납니다.
C#에서 변수와 함께 is는 어떻게 쓰나요?
if (obj is Customer c) { ... }는 obj가 (null이 아닌) Customer인지 검사하고, if 안에서 Customer 타입인 c에 대입합니다. 예전의 var c = obj as Customer; if (c != null) 쌍을 대체합니다. 변수는 검사가 참임이 알려진 곳에서만 확정 할당되므로, if (!(obj is Customer c)) return; 뒤에서는 c를 쓸 수 있습니다.
C#에서 is null과 == null의 차이는 무엇인가요?
x is null은 항상 참조 자체를 검사합니다. x == null은 타입이 == 연산자를 오버로딩했다면 그것을 호출하며, 보통 같은 답을 주지만 보장되지는 않습니다. 반대는 x != null, !(x is null), 또는 C# 9부터 x is not null입니다.
when을 쓰는 switch 패턴 매칭은 C#에서 어떻게 동작하나요?
case 레이블은 패턴과 when 가드를 담을 수 있습니다: case Order o when o.Total > 100:. 패턴이 일치하고 조건이 참일 때만 case가 일치하며, case는 위에서 아래로 시도되므로 더 구체적인 것을 앞에 두세요. 앞의 case가 이미 모두 덮어서 도달할 수 없는 case가 있으면 컴파일러가 CS8120을 보고합니다.
어떤 C# 버전이 어떤 패턴을 추가했나요?
C# 7.0: is와 case의 타입, 상수, var 패턴과 when. C# 8: switch 식, 속성, 튜플, 위치 패턴. C# 9: 관계(> 5), 논리(and, or, not), 단독 타입 패턴. C# 10: 확장 속성 패턴({ Address.City: "Porto" }). C# 11: 목록 패턴([1, .., var last]).