컬렉션 초기화자와 패턴 매칭 기초
값의 모양에 따라 처리를 정합니다.
모양을 그대로 적습니다
이 단원은 두 가지를 봅니다. 값을 만들면서 채우는 문법과 값이 어떤 모양인지 확인하는 문법입니다. 따로 배울 것 같지만 둘은 짝을 이루고 있습니다.
// 만들 때 var p = new Product { Name = "사과" }; int[] a = [1, 2, 3]; // 찾을 때 — 거의 같은 모양입니다 if (p is { Name: "사과" }) { } if (a is [1, 2, 3]) { }
담을 때 적은 모양을 그대로 적으면 찾아집니다. 우연이 아니라 그렇게 설계된 것이고, 이 대칭을 알고 나면 새 패턴을 따로 외우지 않아도 됩니다.
1.5에서 switch
식과 >= 90 같은 관계 패턴을,
2.3에서 객체 초기화자를 이미 보았습니다.
여기서는 그 위에 나머지를 올립니다.
만들고, 모양으로 나누기
아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.
// 대괄호 하나로 목록을 만들면서 채웁니다 List<Product> products = [ new("사과", 3000, true), new("포도", 12000, true), new("자두", 2000, false) ]; foreach (var p in products) Console.WriteLine($"{p.Name} : {Label(p)}"); // 속성의 모양으로 나눕니다. 위에서부터 처음 맞는 하나가 골라집니다. static string Label(Product p) => p switch { { Usable: false } => "지금은 팔지 않습니다", { Price: > 10000 } => "비싼 상품입니다", _ => "보통 상품입니다" }; class Product(string name, int price, bool usable) { public string Name { get; } = name; public int Price { get; } = price; public bool Usable { get; } = usable; }
코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.
new("사과", 3000, true) 에 형식 이름이 없습니다.
왼쪽에 List<Product> 라고 적혀 있으니
컴파일러가 압니다. 클래스는
2.3의 기본 생성자로 적었습니다.
컬렉션을 만들면서 채우기
Add 를 여러 번 부르는 대신 중괄호 안에 값을 나열할
수 있습니다(C# 3.0). 3.2에서 본
컬렉션들이 모두 됩니다.
var list = new List<string> { "사과", "포도" }; var set = new HashSet<int> { 1, 2, 2, 3 }; // 1, 2, 3 // 사전은 두 가지로 적을 수 있습니다 var d1 = new Dictionary<string, int> { { "사과", 3000 }, { "포도", 5000 } }; var d2 = new Dictionary<string, int> { ["사과"] = 3000, ["포도"] = 5000 };
두 사전은 같은 것을 만듭니다. 아래쪽(C# 6.0)이 꺼내 쓸 때의 모양과 같아서 읽기 좋습니다. 같은 키를 두 번 적었을 때 위쪽은 예외가 발생하고 아래쪽은 덮어씁니다.
2.3의 객체 초기화자와도 함께 사용합니다. 읽기 전용 컬렉션 속성에도 중괄호로 값을 추가할 수 있습니다.
var order = new Order { No = "A-100", Buyer = new Buyer { Name = "홍길동", City = "서울" }, // 중첩됩니다 Items = { "사과", "포도" } // Add 를 부릅니다 };
Items = { … } 는 새 목록을 담는 것이 아니라 이미
있는 목록에 추가하는 것입니다. 그래서
get 만 있는 속성에도 적을 수 있습니다.
대괄호로 적는 컬렉션 식
C# 12부터는 대괄호만으로 적습니다. 형식 이름을 반복하지 않아도 되고, 배열이든 목록이든 왼쪽에 적힌 것으로 만들어집니다.
int[] a = [1, 2, 3]; List<int> b = [4, 5]; Span<int> s = [7, 8]; // .. 를 붙이면 그 안의 값이 펼쳐집니다 int[] all = [..a, ..b, 6]; // 1 2 3 4 5 6
.. 를 스프레드라고 합니다. 목록 둘을
이어 붙이거나 앞뒤에 값을 하나 추가할 때 짧게 적힙니다. 빈 컬렉션은
[] 입니다.
is 로 확인하고 그 자리에서 담기
형식을 확인한 뒤 그 형식으로 사용하려면 확인과 변환을 두 번 적어야
했습니다. is 패턴(C# 7.0)은 그것을 한 번에 합니다.
if (v is string) { var s = (string)v; Console.WriteLine(s.Length); }
if (v is string s) { Console.WriteLine(s.Length); }
맞으면 s 에 담기고 아니면 담기지 않습니다.
3.6에서 본
is not null 도 같은 문법이고, 컴파일러가 그 뒤로
null 이 아니라고 판단하는 것도 이 덕분입니다.
값의 모양으로 거르기
속성 패턴은 중괄호 안에 속성 이름과 조건을 적습니다. 여러 개를 적으면 모두 맞아야 합니다.
static string Fee(Order o) => o switch { { Price: > 100000 } => "무료 배송", { City: "서울", Price: > 10000 } => "당일 배송", { Price: < 5000 } => "배송비 3000원", _ => "배송비 2500원" }; // A-100 50000원 서울 : 당일 배송 // B-200 3000원 부산 : 배송비 3000원 // C-300 120000원 서울 : 무료 배송 // D-400 8000원 대구 : 배송비 2500원
속성 안의 속성도 { Buyer.City: "서울" } 처럼 점으로 이어
적을 수 있습니다. 값이 무엇인지가 아니라 어떤 조건에 드는지를 적는
것이 1.5의
switch 와 달라진 점입니다.
튜플 패턴은 값 여럿을 한 번에 봅니다. 괄호로 묶어 넘기고 괄호로 묶어 받습니다.
static string Quadrant(int x, int y) => (x, y) switch { (0, 0) => "원점", (_, 0) => "가로축", (0, _) => "세로축", _ => "그 밖" };
목록 패턴(C# 11)은 길이와 자리를 봅니다. 컬렉션 식과 같은 대괄호입니다.
static string Shape(int[] a) => a switch { [] => "비었습니다", [var only] => $"하나뿐 {only}", [1, .., 9] => "1로 시작해 9로 끝납니다", [var first, .. var rest] => $"첫째 {first}, 나머지 {rest.Length}개" }; // [] => 비었습니다 // [7] => 하나뿐 7 // [1,9] => 1로 시작해 9로 끝납니다 // [1,5,9] => 1로 시작해 9로 끝납니다 // [1,2,3,4] => 첫째 1, 나머지 3개
.. 는 몇 개든 상관없다는 뜻이고,
.. var rest 라고 적으면 그 부분을 받아
둡니다. 컬렉션 식의 스프레드와 같은 기호가 반대 방향으로 사용됩니다.
조건이 패턴으로 적기 어려우면 when 을 덧붙입니다.
3.7의 예외 필터와 같은 낱말이지만
여기서는 그 경우를 선택할지 정하는 조건입니다.
v switch { int n when n > 40 => $"큰 정수 {n}", int n => $"정수 {n}", string s => $"글자 {s.Length}자", null => "값이 없습니다", _ => "그 밖" }
만드는 문법과 찾는 문법
지금까지 본 것을 나란히 놓으면 이렇습니다.
| 만들 때 | 찾을 때 |
|---|---|
| new Product { Name = "사과" } | p is { Name: "사과" } |
| int[] a = [1, 2, 3]; | a is [1, 2, 3] |
| [first, ..rest] | [var first, .. var rest] |
| (x, y) | (x, y) switch { (0, 0) => … } |
등호가 콜론으로 바뀌는 정도의 차이입니다. 새 패턴을 만나면 그것을 만드는 문법을 떠올려 보세요. 대개 같은 모양입니다.
다만 완전히 같지는 않습니다. 만들 때는 [..a, ..b] 처럼
.. 를 여러 번 적을 수 있지만, 찾을 때는 한 번만
적을 수 있습니다. 어디까지가 앞의 나머지이고 어디부터가 뒤의 나머지인지
정할 수 없기 때문입니다.
여기까지가 기초입니다
패턴은 서로 안에 들어갈 수 있습니다. 속성 패턴 안에 목록 패턴을,
그 안에 다시 관계 패턴을 넣는 식입니다. 깊이 겹친 패턴과
record 를 분해하는 패턴은
4부에서 다룹니다.
그리고 패턴이 늘 나은 것은 아닙니다. 조건이 둘뿐이면
if 가 읽기 쉽고, 경우가 여럿이거나 값의 모양으로 나눌
때 패턴이 짧아집니다. 1.5에서 본
모든 경우를 덮어야 한다는 규칙도 그대로입니다.
이 문법, 몇 버전부터 사용할 수 있나요
- 3.0컬렉션 초기화자 · 객체 초기화자
- 6.0인덱스 초기화자 — ["사과"] = 3000
- 7.0is 패턴 — 확인하면서 담기
- 8.0switch 식 · 속성 패턴 · 튜플 패턴
- 9.0관계 패턴 · 논리 패턴(and · or · not)
- 11.0목록 패턴 — [1, .., 9]
- 12.0컬렉션 식 · 스프레드 — [..a, ..b]
패턴 매칭은 한 버전에 들어온 기능이 아니라 일곱 해에 걸쳐 조금씩
늘어난 것입니다. C# 7.0의 is 패턴으로
시작해 버전마다 볼 수 있는 모양이 추가되었고, C# 12의 컬렉션 식이 들어오면서
만드는 쪽과 찾는 쪽의 대칭이 완성되었습니다. 목록 패턴이
11.0, 컬렉션 식이 12.0으로 찾는 쪽이 한 해 먼저인 것도
그래서입니다.
직접 해보기
아래 코드를 switch 식과 속성 패턴으로 바꿔보세요.
판단 순서가 바뀌지 않도록 주의합니다.
static string Fee(Order o) { if (o.Price > 100000) return "무료 배송"; if (o.City == "서울" && o.Price > 10000) return "당일 배송"; if (o.Price < 5000) return "배송비 3000원"; return "배송비 2500원"; }
띄어쓰기로 나뉜 명령을 string[] 으로 받아 목록 패턴으로
해석해보세요. list·add 이름·remove 이름
셋을 알아듣고, 빈 입력·이름이 둘 이상인 add·모르는 명령도 각각
다르게 답합니다.
- 컬렉션은 중괄호에 값을 나열해 만들면서 채웁니다. 사전은
["사과"] = 3000이 읽기 좋습니다. - C# 12부터는 대괄호로 적고,
..로 다른 컬렉션을 펼쳐 넣습니다. is패턴은 확인과 담기를 한 번에 합니다.- 속성 패턴은
{ Price: > 10000 }, 목록 패턴은[1, .., 9]로 값의 모양을 적습니다. - 패턴으로 적기 어려운 조건은
when을 덧붙입니다. - 만드는 문법과 찾는 문법이 짝을 이룹니다. 등호가 콜론으로 바뀌는 정도의 차이입니다.
- 경우가 여럿이거나 모양으로 나눌 때 패턴이 짧아집니다. 조건이 둘뿐이면
if가 낫습니다.
C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.