C# 9.0의 패턴 매칭 변경 사항
메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는관련
사양서문서에서 기능 사양서를 C# 언어 표준으로 채택하는 절차에 대한 자세한 정보를 얻을 수 있습니다.
C# 9.0에 대한 패턴 일치에 대한 몇 가지 향상된 기능을 고려하고 있으며, 이는 자연스러운 시너지 효과가 있고 다양한 일반적인 프로그래밍 문제를 해결하기 위해 잘 작동합니다.
- https://github.com/dotnet/csharplang/issues/2925 형식 패턴
- 새 결합자의 우선 순위를 적용하거나 강조하기 위해 괄호로 묶인 패턴 https://github.com/dotnet/csharplang/issues/1350
- 서로 다른 두 패턴이 모두 일치해야 하는
and
결합 패턴 https://github.com/dotnet/csharplang/issues/1350. - 서로 다른 두 패턴 중 하나가 일치해야 하는 https://github.com/dotnet/csharplang/issues/1350 이접
or
패턴. - 지정된 패턴 일치시킬 없는 부정
not
패턴을 https://github.com/dotnet/csharplang/issues/1350. 그리고 - https://github.com/dotnet/csharplang/issues/812 입력 값이 지정된 상수보다 작거나 같아야 하는 관계형 패턴입니다.
괄호로 둘러싸인 패턴
괄호가 있는 패턴은 프로그래머가 모든 패턴에 괄호를 넣을 수 있도록 합니다. 이는 C# 8.0의 기존 패턴에 그다지 유용하지는 않습니다. 그러나 새로운 패턴 조합기는 프로그래머가 재정의할 수 있는 우선 순위를 도입합니다.
primary_pattern
: parenthesized_pattern
| // all of the existing forms
;
parenthesized_pattern
: '(' pattern ')'
;
유형 패턴
유형을 패턴으로 허용합니다.
primary_pattern
: type-pattern
| // all of the existing forms
;
type_pattern
: type
;
기존 is-type-expression이 is-pattern-expression로 수정되어, 이 패턴은 형식 패턴를 포함하지만, 우리는 컴파일러가 생성한 구문 트리를 변경하지 않을 것입니다.
한 가지 미묘한 구현 문제는 이 문법이 모호하다는 것입니다.
a.b
같은 문자열은 유자격 이름(형식 컨텍스트 내에서) 또는 점으로 구분된 표현(표현식 컨텍스트 내에서)으로 구문 분석할 수 있습니다. 컴파일러는 이미 e is Color.Red
같은 항목을 처리하기 위해 점선 식과 동일한 정규화된 이름을 처리할 수 있습니다. 컴파일러의 의미 분석은 이 구조를 지원하기 위해 점이 포함된 표현식과 같은 (구문) 상수 패턴을 타입으로 바인딩하여 바인딩된 타입 패턴으로 처리할 수 있도록 더욱 확장될 것입니다.
이 변경 후에는 작성할 수 있습니다.
void M(object o1, object o2)
{
var t = (o1, o2);
if (t is (int, string)) {} // test if o1 is an int and o2 is a string
switch (o1) {
case int: break; // test if o1 is an int
case System.String: break; // test if o1 is a string
}
}
관계형 패턴
관계형 패턴을 사용하면 프로그래머가 상수 값과 비교할 때 입력 값이 관계형 제약 조건을 충족해야 한다는 것을 표현할 수 있습니다.
public static LifeStage LifeStageAtAge(int age) => age switch
{
< 0 => LifeStage.Prenatal,
< 2 => LifeStage.Infant,
< 4 => LifeStage.Toddler,
< 6 => LifeStage.EarlyChild,
< 12 => LifeStage.MiddleChild,
< 20 => LifeStage.Adolescent,
< 40 => LifeStage.EarlyAdult,
< 65 => LifeStage.MiddleAdult,
_ => LifeStage.LateAdult,
};
관계형 패턴은 식에서 동일한 형식의 두 피연산자를 사용하여 이러한 이진 관계형 연산자를 지원하는 모든 기본 제공 형식에서 관계형 연산자 <
, <=
, >
및 >=
지원합니다. 특히 sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
, nint
및 nuint
대한 관계형 패턴을 모두 지원합니다.
primary_pattern
: relational_pattern
;
relational_pattern
: '<' relational_expression
| '<=' relational_expression
| '>' relational_expression
| '>=' relational_expression
;
식은 반드시 상수 값으로 평가되어야 합니다. 상수 값이 double.NaN
또는 float.NaN
경우 오류가 발생합니다. 식이 null 상수일 경우, 이는 오류입니다.
입력이 기본 제공 이진 관계형 연산자가 정의된 적합한 형식일 때, 이 입력이 왼쪽 피연산자로, 주어진 상수가 오른쪽 피연산자로 적용 가능한 경우, 그 연산자의 평가 결과가 관계형 패턴의 의미로 사용됩니다. 그렇지 않으면 명시적 nullable 또는 unboxing 변환을 사용하여 입력을 식 형식으로 변환합니다. 이러한 변환이 없으면 컴파일 시간 오류입니다. 변환이 실패할 경우 패턴이 일치하지 않는 것으로 간주됩니다. 변환이 성공하면 패턴 일치 작업의 결과는 e
변환된 입력인 식 e OP v
계산한 결과이며, OP
관계형 연산자이며 v
상수 식입니다.
패턴 결합자
패턴 결합자는 and
사용하여 두 개의 서로 다른 패턴(and
반복 사용으로 여러 패턴으로 확장 가능), or
(ditto)를 사용하는 두 가지 패턴 중 하나 또는 not
사용하는 패턴의 부정 일치를 허용할 있습니다.
조합기의 일반적인 사용은 관용구입니다.
if (e is not null) ...
현재 관용구 e is object
보다 읽기 쉬운 이 패턴은 null이 아닌 값을 확인 중임을 명확하게 나타냅니다.
and
및 or
결합자는 값 범위를 테스트하는 데 유용합니다.
bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
이 예제는 and
가 or
보다 더 높은 구문 분석 우선 순위(즉, 더 밀접하게 결합됨)를 가진다는 것을 보여줍니다. 프로그래머가 괄호가 있는 패턴 사용하여 우선 순위를 명시적으로 지정할 수 있습니다.
bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
모든 패턴과 마찬가지로 이러한 결합자는 중첩 패턴, is-pattern-expression, 스위치 식및 switch 문의 사례 레이블 패턴을 포함하여 패턴이 필요한 모든 컨텍스트에서 사용할 수 있습니다.
pattern
: disjunctive_pattern
;
disjunctive_pattern
: disjunctive_pattern 'or' conjunctive_pattern
| conjunctive_pattern
;
conjunctive_pattern
: conjunctive_pattern 'and' negated_pattern
| negated_pattern
;
negated_pattern
: 'not' negated_pattern
| primary_pattern
;
primary_pattern
: // all of the patterns forms previously defined
;
6.2.5 문법 모호성으로 변경
형식 패턴도입되어 토큰 =>
앞에 제네릭 형식이 나타날 수 있습니다. 따라서 형식 인수 목록을 시작하는 <
을(를) 명확히 할 수 있도록 §6.2.5 문법 모호성에 나열된 토큰 집합에 =>
을(를) 추가합니다.
https://github.com/dotnet/roslyn/issues/47614을 참조하세요.
제안된 변경 내용과 관련된 문제 열기
관계형 연산자의 구문
and
, or
및 not
상황에 맞는 키워드인가요? 그렇다면 기존 호환성을 깨뜨리는 변경이 있습니까? (예: 선언 패턴의 지정자로 사용하는 것과 비교하여)
관계형 연산자의 의미 체계(예: 형식)
관계형 연산자를 사용하여 식에서 비교할 수 있는 모든 기본 형식을 지원해야 합니다. 간단한 경우의 의미는 분명합니다.
bool IsValidPercentage(int x) => x is >= 0 and <= 100;
그러나 입력이 이러한 기본 형식이 아닌 경우 어떤 형식으로 변환하려고 할까요?
bool IsValidPercentage(object x) => x is >= 0 and <= 100;
입력 형식이 이미 비교 가능한 기본 형식인 경우 비교의 형식이라고 제안했습니다. 그러나 입력이 비교 가능한 기본형이 아닌 경우, 우리는 관계형을 오른쪽에 있는 상수의 유형에 대한 암시적 유형 테스트를 포함하는 것으로 처리합니다. 프로그래머가 둘 이상의 입력 형식을 지원하려는 경우 명시적으로 수행해야 합니다.
bool IsValidPercentage(object x) => x is
>= 0 and <= 100 or // integer tests
>= 0F and <= 100F or // float tests
>= 0D and <= 100D; // double tests
결과: 관계형에는 관계형 오른쪽에 있는 상수 형식에 대한 암시적 형식 테스트가 포함됩니다.
왼쪽에서 오른쪽으로 and
형식 정보를 흐르게 하는 것
and
조합기를 작성할 때 최상위 형식에 대해 왼쪽에서 학습한 형식 정보가 오른쪽으로 흐를 수 있다는 것이 제안되었습니다. 예를 들어
bool isSmallByte(object o) => o is byte and < 100;
여기서 두 번째 패턴으로 P
에서 축소된 유형은 다음과 같이 정의됩니다.
-
P
형식 패턴인 경우 축소된 형식 형식 패턴 형식입니다. -
P
이 선언 패턴인 경우, 의 좁혀진 형식은/는 해당 선언 패턴의 형식입니다. 가 명시적 형식을 제공하는 재귀 패턴일 경우,의 좁은 형식은 해당 형식입니다. 가 규칙에 따라 일치하면, 축소된 형식 은형식입니다. -
P
이 null 상수가 아닌 상수 패턴이고, 식에 상수 식 변환에서 입력 형식로의 변환이 없는 경우, 축소된 형식은 상수의 형식입니다. -
P
가 상수 식에 대해 입력 형식으로 상수 식 변환이 없는 관계형 패턴이라면, 축소된 형식은 상수의 형식입니다. -
P
이or
패턴인 경우, 로 축소된 형식이(가) 존재하는 경우 하위 패턴의 로 축소된 형식의 공통 형식입니다. 이를 위해 공통 유형 알고리즘은 동일성, boxing 및 암시적 참조 변환만을 고려하며, 괄호가 있는 패턴을 무시하고or
패턴 시퀀스의 모든 하위 패턴을 검토합니다. -
P
이and
패턴인 경우, 의 축소된 형식는 오른쪽 패턴의 축소된 형식입니다. 또한 왼쪽 패턴의 축소된 형식은 오른쪽 패턴의 입력 형식입니다. - 그렇지 않으면
P
는 축소된 형식의P
입력 형식입니다.
결과: 위의 축소 의미 체계가 구현되었습니다.
변수 정의 및 명확한 할당
or
및 not
패턴을 추가하면 패턴 변수 및 명확한 할당과 관련하여 흥미로운 새로운 문제가 발생합니다. 변수는 일반적으로 한 번만 선언할 수 있으므로, 패턴이 일치할 때 or
패턴의 한쪽에 선언된 패턴 변수는 확실히 할당되지 않을 가능성이 있어 보입니다. 마찬가지로 not
패턴 내에 선언된 변수는 패턴이 일치할 때 확실히 할당되지 않을 것으로 예상됩니다. 이 문제를 해결하는 가장 간단한 방법은 이러한 컨텍스트에서 패턴 변수 선언을 금지하는 것입니다. 그러나 너무 제한적일 수 있습니다. 고려해야 할 다른 방법이 있습니다.
고려해야 할 한 가지 시나리오는 다음과 같습니다.
if (e is not int i) return;
M(i); // is i definitely assigned here?
이를 지원하는 것은 부정 조건 if
문에 대한 지원을 추가하는 것보다 (프로그래머의 관점에서) 더 간단합니다. 이러한 지원을 추가하더라도 프로그래머는 위의 코드 조각이 작동하지 않는 이유를 궁금해할 것입니다. 반면에
이와 관련된 것은 분리 패턴에서의 명시적 할당 문제입니다.
if (e is 0 or int i)
{
M(i); // is i definitely assigned here?
}
입력이 0이 아닌 경우에만 i
확실히 할당될 것으로 예상합니다. 그러나 입력이 블록 내에서 0인지 여부를 알 수 없으므로 i
확실히 할당되지 않습니다. 그러나 상호 배제하는 다양한 패턴에서 i
의 선언을 허용하면 어떻게 될까요?
if ((e1, e2) is (0, int i) or (int i, 0))
{
M(i);
}
여기서는 i
변수가 블록 내에 확실히 할당되고 0 요소가 발견되면 튜플의 다른 요소에서 값을 가져옵니다.
또한 사례 블록의 모든 경우에 변수를 정의(곱)하도록 허용하는 것이 좋습니다.
case (0, int x):
case (int x, 0):
Console.WriteLine(x);
이 작업을 수행하려면 이러한 여러 정의가 허용되는 위치와 이러한 변수가 확실히 할당된 것으로 간주되는 조건을 신중하게 정의해야 합니다.
내가 조언하듯이 이러한 작업을 나중으로 연기하기로 선택한다면, C# 9에서 이렇게 말할 수 있습니다.
-
not
또는or
아래에 패턴 변수를 선언할 수 없습니다.
그런 다음 우리는 나중에 그 완화의 잠재적인 가치를 이해할 수 있는 몇 가지 경험을 쌓을 시간이 있을 것입니다.
결과: 패턴 변수는 not
또는 or
패턴 아래에 선언할 수 없습니다.
진단, 포괄 및 완전성
이러한 새로운 패턴 양식은 진단 가능한 프로그래머 오류에 대한 많은 새로운 기회를 소개합니다. 진단할 오류 종류와 이를 수행하는 방법을 결정해야 합니다. 다음은 몇 가지 예입니다.
case >= 0 and <= 100D:
입력이 int
와 double
일 수 없으므로 이 경우 일치시킬 수 없습니다. 일치시킬 수 없는 사례를 감지하면 이미 오류가 발생하지만 해당 문구("이전 사례에서 이미 처리된 스위치 사례" 및 "스위치 식의 이전 팔에서 패턴이 이미 처리되었습니다.")는 새 시나리오에서 오해의 소지가 있을 수 있습니다. 패턴이 입력과 일치하지 않는다고 말하기 위해 문구를 수정해야 할 수도 있습니다.
case 1 and 2:
마찬가지로, 값이 1
과 2
일 수 없기 때문에 오류가 발생합니다.
case 1 or 2 or 3 or 1:
이 경우 일치시킬 수 있지만, 끝에 있는 or 1
은 패턴에 의미를 더하지 않습니다. 복합 패턴의 일부 결합 또는 분리가 패턴 변수를 정의하거나 일치하는 값 집합에 영향을 주지 않을 때마다 오류를 생성하는 것을 목표로 하는 것이 좋습니다.
case < 2: break;
case 0 or 1 or 2 or 3 or 4 or 5: break;
여기서 0 or 1 or
첫 번째 사례에서 해당 값을 처리했으므로 두 번째 사례에 아무것도 추가하지 않습니다. 이것 역시 오류에 해당됩니다.
byte b = ...;
int x = b switch { <100 => 0, 100 => 1, 101 => 2, >101 => 3 };
이와 같은 스위치 식은 전체
C# 8.0에서 byte
형식의 입력이 있는 스위치 식은 패턴이 모든 항목과 일치하는 최종 암(무시 패턴 또는 var 패턴)을 포함하는 경우에만 철저한 것으로 간주됩니다. 모든 고유 byte
값에 대해 각 분기를 갖고 있는 스위치 식조차도 C# 8에서는 완전한 것으로 간주되지 않습니다. 관계형 패턴의 완전성을 제대로 처리하려면 이 사례도 처리해야 합니다. 기술적으로는 호환성이 손상되는 변경이지만, 대부분의 사용자가 눈치채지 못할 가능성이 높습니다.
C# feature specifications