패턴 일치 - is
및 식, 연산자 and
및 or
not
패턴 switch
식, switch 문 및 switch 식을 사용하여 is
입력 식을 다양한 특성과 일치시킬 수 있습니다. C#은 선언, 형식, 상수, 관계형, 속성, 목록, var 및 삭제를 비롯한 여러 패턴을 지원합니다. 패턴은 부울 논리 키워드 and
, or
및 not
를 사용하여 결합할 수 있습니다.
다음 C# 식과 문은 패턴 일치를 지원합니다.
해당 구문에서 다음 패턴 중 하나에 대해 입력 식을 매칭할 수 있습니다.
- 선언 패턴: 식의 런타임 형식을 확인하고 일치가 성공하면 식 결과를 선언된 변수에 할당합니다.
- 형식 패턴: 식의 런타임 형식을 확인합니다. C# 9.0에 도입됨.
- 상수 패턴: 식 결과가 지정된 상수와 같은지 테스트합니다.
- 관계형 패턴: 식 결과를 지정된 상수와 비교합니다. C# 9.0에 도입됨.
- 논리 패턴: 식이 패턴의 논리적 조합과 일치하는지 테스트합니다. C# 9.0에 도입됨.
- 속성 패턴: 식의 속성 또는 필드가 중첩 패턴과 일치하는지 테스트합니다.
- 위치 패턴: 식 결과를 분해하고 결과 값이 중첩 패턴과 일치하는지 테스트합니다.
var
패턴: 모든 식을 매칭하고 결과를 선언된 변수에 할당합니다.- 무시 패턴: 모든 식을 매칭합니다.
- 목록 패턴: 시퀀스 요소가 해당 중첩 패턴과 일치하는지 테스트합니다. C# 11에 도입되었습니다.
논리, 속성, 위치 및 목록 패턴은 재귀 패턴입니다. 즉, 중첩 패턴을 포함할 수 있습니다.
이러한 패턴을 사용하여 데이터 기반 알고리즘을 빌드하는 방법에 대한 예제는 자습서: 패턴 일치를 사용하여 형식 기반 및 데이터 기반 알고리즘 빌드를 참조하세요.
선언 및 형식 패턴
식의 런타임 형식이 지정된 형식과 호환되는지 확인하기 위해 선언 및 형식 패턴을 사용합니다. 선언 패턴을 사용하면 새 지역 변수를 선언할 수도 있습니다. 선언 패턴이 식과 일치하는 경우 다음 예제와 같이 해당 변수에 변환된 식 결과가 할당됩니다.
object greeting = "Hello, World!";
if (greeting is string message)
{
Console.WriteLine(message.ToLower()); // output: hello, world!
}
형식 T
이 있는 선언 패턴은 식 결과가 null이 아니고 다음 조건 중 어느 것이라도 true인 경우 식과 일치합니다.
식 결과의 런타임 형식은
T
입니다.식 결과의 런타임 형식은
T
형식에서 파생되거나 인터페이스T
를 구현합니다. 또는 여기에서T
로 또 다른 암시적 참조 변환이 존재합니다. 다음 예제에서는 이 조건이 참인 경우의 두 가지 사례를 보여 줍니다.var numbers = new int[] { 10, 20, 30 }; Console.WriteLine(GetSourceLabel(numbers)); // output: 1 var letters = new List<char> { 'a', 'b', 'c', 'd' }; Console.WriteLine(GetSourceLabel(letters)); // output: 2 static int GetSourceLabel<T>(IEnumerable<T> source) => source switch { Array array => 1, ICollection<T> collection => 2, _ => 3, };
앞의 예제에서
GetSourceLabel
메서드를 처음 호출할 때 인수의 런타임 형식int[]
가 Array 형식에서 파생되기 때문에 첫 번째 패턴은 인수 값과 일치합니다.GetSourceLabel
메서드에 대한 두 번째 호출에서 인수의 런타임 형식 List<T>는 Array 형식에서 파생되지 않지만 ICollection<T> 인터페이스를 구현합니다.식 결과의 런타임 형식은 기본 형식
T
를 사용하는 null 허용 값 형식입니다.
다음 예제에서는 마지막 두 조건을 보여 줍니다.
int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
Console.WriteLine(a + b); // output: 30
}
식의 형식만 확인하려는 경우 다음 예제와 같이 변수의 이름을 대신하여 무시 _
을 사용할 수 있습니다.
public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}
public static class TollCalculator
{
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
Car _ => 2.00m,
Truck _ => 7.50m,
null => throw new ArgumentNullException(nameof(vehicle)),
_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};
}
이 목적을 위해 C# 9.0부터 다음 예제와 같이 형식 패턴을 사용할 수 있습니다.
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
Car => 2.00m,
Truck => 7.50m,
null => throw new ArgumentNullException(nameof(vehicle)),
_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};
선언 패턴과 마찬가지로, 식 결과가 null이 아니고 해당 런타임 형식이 위에 나열된 조건 중 하나를 충족하는 경우 형식 패턴이 식과 일치합니다.
null이 아닌지 확인하려면 다음 예제와 같이 부정 상null
수 패턴을 사용할 수 있습니다.
if (input is not null)
{
// ...
}
자세한 내용은 기능 제안 노트의 선언 패턴 및 형식 패턴 섹션을 참조하세요.
상수 패턴
다음 예제와 같이 상수 패턴을 사용하여 식 결과가 지정된 상수와 같은지 테스트합니다.
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
1 => 12.0m,
2 => 20.0m,
3 => 27.0m,
4 => 32.0m,
0 => 0.0m,
_ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};
상수 패턴에서 다음과 같은 상수 식을 사용할 수 있습니다.
식은 상수 형식으로 변환할 수 있는 형식이어야 합니다. C# 11 이상 버전의 상수 문자열에 대해 형식이 이거나 ReadOnlySpan<char>
일치시킬 수 있는 식입니다Span<char>
.
다음 예제와 같이 null
을 확인하려면 상수 패턴을 사용합니다.
if (input is null)
{
return;
}
컴파일러는 x is null
식이 평가될 때 사용자 오버로드된 같음 연산자 ==
가 호출되지 않도록 보장합니다.
C# 9.0부터 다음 예제와 같이 부정 상null
수 패턴을 사용하여 null이 아닌지 확인할 수 있습니다.
if (input is not null)
{
// ...
}
자세한 내용은 기능 제안 노트의 상수 패턴 섹션을 참조하세요.
관계형 패턴
C# 9.0부터 다음 예제와 같이 식 결과를 상수와 비교하려면 관계형 패턴을 사용합니다.
Console.WriteLine(Classify(13)); // output: Too high
Console.WriteLine(Classify(double.NaN)); // output: Unknown
Console.WriteLine(Classify(2.4)); // output: Acceptable
static string Classify(double measurement) => measurement switch
{
< -4.0 => "Too low",
> 10.0 => "Too high",
double.NaN => "Unknown",
_ => "Acceptable",
};
관계형 패턴에서 관계형 연산자<
, >
, <=
또는 >=
를 사용할 수 있습니다. 관계형 패턴의 오른쪽 부분은 상수 식이어야 합니다. 상수 식은 정수, 부동 소수점, char 또는 enum 형식일 수 있습니다.
식 결과가 특정 범위에 있는지 확인하려면 다음 예제와 같이 결합 and
패턴을 기준으로 매칭합니다.
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14))); // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19))); // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17))); // output: winter
static string GetCalendarSeason(DateTime date) => date.Month switch
{
>= 3 and < 6 => "spring",
>= 6 and < 9 => "summer",
>= 9 and < 12 => "autumn",
12 or (>= 1 and < 3) => "winter",
_ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};
식 결과가 null
인 경우 또는 nullable 또는 unboxing 변환을 통해 상수 형식으로 변환하지 못하는 경우 관계형 패턴이 식과 일치하지 않습니다.
자세한 내용은 기능 제안 노트의 관계형 패턴 섹션을 참조하세요.
논리 패턴
C# 9.0부터 not
, and
, or
패턴 조합기를 사용하여 다음과 같은 논리 패턴을 만듭니다.
부정
not
부정된 패턴이 식과 일치하지 않을 때 식과 일치하는 패턴입니다. 다음 예제에서는 식이 null이 아닌지 확인하기 위해 상수null
패턴을 부정하는 방법을 보여 줍니다.if (input is not null) { // ... }
결합형
and
두 패턴이 식과 일치하는 경우 식과 일치하는 패턴입니다. 다음 예제에서는 관계형 패턴을 결합하여 값이 특정 범위에 있는지 확인하는 방법을 보여 줍니다.Console.WriteLine(Classify(13)); // output: High Console.WriteLine(Classify(-100)); // output: Too low Console.WriteLine(Classify(5.7)); // output: Acceptable static string Classify(double measurement) => measurement switch { < -40.0 => "Too low", >= -40.0 and < 0 => "Low", >= 0 and < 10.0 => "Acceptable", >= 10.0 and < 20.0 => "High", >= 20.0 => "Too high", double.NaN => "Unknown", };
분리형
or
다음 예제와 같이 두 패턴이 식과 일치할 때 식과 일치하는 패턴입니다.Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19))); // output: winter Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9))); // output: autumn Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11))); // output: spring static string GetCalendarSeason(DateTime date) => date.Month switch { 3 or 4 or 5 => "spring", 6 or 7 or 8 => "summer", 9 or 10 or 11 => "autumn", 12 or 1 or 2 => "winter", _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."), };
앞의 예제에서 볼 수 있듯이 패턴에서 조합기 패턴을 반복적으로 사용할 수 있습니다.
확인 우선 순위 및 순서
다음 목록은 가장 높은 우선 순위부터 가장 낮은 것으로 패턴 조합을 정렬합니다.
not
and
or
우선 순위를 명시적으로 지정하려면 다음 예제와 같이 괄호를 사용합니다.
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
참고
패턴을 확인하는 순서는 정의되지 않습니다. 런타임에 or
및 and
패턴의 오른쪽 중첩 패턴을 먼저 확인할 수 있습니다.
자세한 내용은 기능 제안 노트의 패턴 조합기 섹션을 참조하세요.
속성 패턴
다음 예제와 같이 속성 패턴을 사용하여 식의 속성 또는 필드를 중첩된 패턴과 일치시킬 수 있습니다.
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };
식 결과가 null이 아니고 모든 중첩 패턴이 식 결과의 해당 속성 또는 필드와 일치하는 경우 속성 패턴은 식과 일치합니다.
다음 예제와 같이 런타임 형식 검사 및 변수 선언을 속성 패턴에 추가할 수도 있습니다.
Console.WriteLine(TakeFive("Hello, world!")); // output: Hello
Console.WriteLine(TakeFive("Hi!")); // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abc
static string TakeFive(object input) => input switch
{
string { Length: >= 5 } s => s.Substring(0, 5),
string s => s,
ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
ICollection<char> symbols => new string(symbols.ToArray()),
null => throw new ArgumentNullException(nameof(input)),
_ => throw new ArgumentException("Not supported input type."),
};
속성 패턴은 재귀 패턴입니다. 즉, 모든 패턴을 중첩 패턴으로 사용할 수 있습니다. 다음 예제와 같이 중첩 패턴에 대해 데이터의 일부를 매칭하려면 속성 패턴을 사용합니다.
public record Point(int X, int Y);
public record Segment(Point Start, Point End);
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start: { Y: 0 } } or { End: { Y: 0 } };
앞의 예제에서는 C# 9.0 이상에서 사용할 수 있는 두 가지 기능인 or
패턴 조합기와레코드 형식을 사용합니다.
C# 10부터 속성 패턴 내에서 중첩된 속성 또는 필드를 참조할 수 있습니다. 이 기능을 확장 속성 패턴이라고 합니다. 예를 들어 이전 예제의 메서드를 다음과 동일한 코드로 리팩터링할 수 있습니다.
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
자세한 내용은 기능 제안 메모의 속성 패턴 섹션과 확장 속성 패턴 기능 제안 노트를 참조하세요.
팁
속성 패턴 단순화(IDE0170) 스타일 규칙을 사용하여 확장 속성 패턴을 사용할 위치를 제안하여 코드 가독성을 향상시킬 수 있습니다.
위치 패턴
다음 예제와 같이 위치 패턴을 사용하여 식 결과를 분해하고 결과 값을 해당 중첩 패턴과 일치시킬 수 있습니다.
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
static string Classify(Point point) => point switch
{
(0, 0) => "Origin",
(1, 0) => "positive X basis end",
(0, 1) => "positive Y basis end",
_ => "Just a point",
};
앞의 예제에서 식의 형식은 식 결과를 분해하는 데 사용되는 분해 메서드를 포함합니다. 위치 패턴에 대해 튜플 형식의 식을 매칭할 수도 있습니다. 이 방식으로 다음 예제와 같이 다양한 패턴에 대해 여러 입력을 매칭할 수 있습니다.
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
=> (groupSize, visitDate.DayOfWeek) switch
{
(<= 0, _) => throw new ArgumentException("Group size must be positive."),
(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
(>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
(>= 10, DayOfWeek.Monday) => 30.0m,
(>= 5 and < 10, _) => 12.0m,
(>= 10, _) => 15.0m,
_ => 0.0m,
};
앞의 예제에서는 C# 9.0 이상에서 사용할 수 있는 관계형 및 논리 패턴을 사용합니다.
다음 예제와 같이 위치 패턴에서 튜플 요소 및 Deconstruct
매개 변수의 이름을 사용할 수 있습니다.
var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}"); // output: Sum of [1 2 3] is 6
}
static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
int sum = 0;
int count = 0;
foreach (int number in numbers)
{
sum += number;
count++;
}
return (sum, count);
}
다음 방법 중 하나로 위치 패턴을 확장할 수도 있습니다.
다음 예제와 같이 런타임 형식 검사 및 변수 선언을 추가합니다.
public record Point2D(int X, int Y); public record Point3D(int X, int Y, int Z); static string PrintIfAllCoordinatesArePositive(object point) => point switch { Point2D (> 0, > 0) p => p.ToString(), Point3D (> 0, > 0, > 0) p => p.ToString(), _ => string.Empty, };
앞의 예제에서는
Deconstruct
메서드를 암시적으로 제공하는 위치 레코드를 사용합니다.다음 예제와 같이 위치 패턴 내에서 속성 패턴을 사용합니다.
public record WeightedPoint(int X, int Y) { public double Weight { get; set; } } static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
다음 예제와 같이 앞에 나온 두 가지 사용을 결합합니다.
if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p) { // .. }
위치 패턴은 재귀 패턴입니다. 즉, 모든 패턴을 중첩 패턴으로 사용할 수 있습니다.
자세한 내용은 기능 제안 노트의 위치 패턴 섹션을 참조하세요.
var
패턴
패턴을 사용하여 을var
비롯한null
모든 식을 일치시키고 다음 예제와 같이 결과를 새 지역 변수에 할당합니다.
static bool IsAcceptable(int id, int absLimit) =>
SimulateDataFetch(id) is var results
&& results.Min() >= -absLimit
&& results.Max() <= absLimit;
static int[] SimulateDataFetch(int id)
{
var rand = new Random();
return Enumerable
.Range(start: 0, count: 5)
.Select(s => rand.Next(minValue: -10, maxValue: 11))
.ToArray();
}
var
패턴은 부울 식 내에서 중간 계산 결과를 저장하기 위해 임시 변수가 필요한 경우 유용합니다. 다음 예제와 var
같이 식 또는 문의 가드 switch
에 when
대해 더 많은 검사를 수행해야 하는 경우에도 패턴을 사용할 수 있습니다.
public record Point(int X, int Y);
static Point Transform(Point point) => point switch
{
var (x, y) when x < y => new Point(-x, y),
var (x, y) when x > y => new Point(x, -y),
var (x, y) => new Point(x, y),
};
static void TestTransform()
{
Console.WriteLine(Transform(new Point(1, 2))); // output: Point { X = -1, Y = 2 }
Console.WriteLine(Transform(new Point(5, 2))); // output: Point { X = 5, Y = -2 }
}
앞의 예제에서 패턴 var (x, y)
은 위치 패턴(var x, var y)
과 동일합니다.
var
패턴에서 선언된 변수의 형식은 패턴에 대해 일치하는 식의 컴파일 시간 형식입니다.
자세한 내용은 기능 제안 노트의 변수 패턴 섹션을 참조하세요.
무시 패턴
다음 예제와 같이 무시 패턴을_
사용하여 을 비롯한 null
모든 식과 일치합니다.
Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday)); // output: 5.0
Console.WriteLine(GetDiscountInPercent(null)); // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10)); // output: 0.0
static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
DayOfWeek.Monday => 0.5m,
DayOfWeek.Tuesday => 12.5m,
DayOfWeek.Wednesday => 7.5m,
DayOfWeek.Thursday => 12.5m,
DayOfWeek.Friday => 5.0m,
DayOfWeek.Saturday => 2.5m,
DayOfWeek.Sunday => 2.0m,
_ => 0.0m,
};
앞의 예제에서 무시 패턴은 null
을 처리하는 데 사용되고 DayOfWeek 열거형의 해당 멤버가 없는 정수 값을 사용합니다. 이렇게 하면 예제의 switch
식이 가능한 모든 입력 값을 처리할 수 있습니다. 식에서 무시 패턴을 사용하지 않으며 식의 패턴 중 입력과 일치하는 것이 없는 경우 런타임은 switch
예외를 throw합니다. switch
식이 가능한 모든 입력 값을 처리하지 않는 경우 컴파일러에서 경고를 생성합니다.
무시 패턴은 식이나 switch
문의 패턴 is
일 수 없습니다. 이 경우 모든 식과 매칭하려면 무시와 함께 var
패턴을 사용합니다(var _
).
자세한 내용은 기능 제안 노트의 무시 패턴 섹션을 참조하세요.
괄호로 묶인 패턴
C# 9.0부터 패턴을 괄호로 묶을 수 있습니다. 일반적으로 다음 예제와 같이 논리 패턴에서 우선 순위를 강조 표시하거나 변경하기 위해 괄호를 사용합니다.
if (input is not (float or double))
{
return;
}
목록 패턴
C# 11부터 다음 예제와 같이 배열 또는 목록을 패턴 시퀀 스와 일치시킬 수 있습니다.
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
Console.WriteLine(numbers is [1, 2, 3, 4]); // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True
앞의 예제와 같이 각 중첩 패턴이 입력 시퀀스의 해당 요소와 일치할 때 목록 패턴이 일치합니다. 목록 패턴 내에서 패턴을 사용할 수 있습니다. 요소를 일치하려면 다음 예제와 같이 무시 패턴을 사용하거나 요소인 var 패턴을 캡처하려는 경우 를 사용합니다.
List<int> numbers = new() { 1, 2, 3 };
if (numbers is [var first, _, _])
{
Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.
앞의 예제는 전체 입력 시퀀스를 목록 패턴과 일치합니다. 입력 시퀀스의 시작 또는/및 끝에 있는 요소만 일치하려면 다음 예제와 같이 목록 패턴 내에서 조각 패턴을..
사용합니다.
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]); // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]); // False
Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // True
Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False
조각 패턴은 0개 이상의 요소와 일치합니다. 목록 패턴에서 최대 하나의 조각 패턴을 사용할 수 있습니다.
다음 예제와 같이 조각 패턴 내에 하위패턴을 중첩할 수도 있습니다.
void MatchMessage(string message)
{
var result = message is ['a' or 'A', .. var s, 'a' or 'A']
? $"Message {message} matches; inner part is {s}."
: $"Message {message} doesn't match.";
Console.WriteLine(result);
}
MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
MatchMessage("apron"); // output: Message apron doesn't match.
void Validate(int[] numbers)
{
var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
Console.WriteLine(result);
}
Validate(new[] { -1, 0, 1 }); // output: not valid
Validate(new[] { -1, 0, 0, 1 }); // output: valid
자세한 내용은 목록 패턴 기능 제안 참고를 참조하세요.
C# 언어 사양
자세한 내용은 다음 기능 제안 노트를 참조하세요.
- C# 7 - 패턴 일치
- C# 8 - 재귀 패턴 일치
- C# 9 - 패턴 일치 업데이트
- C# 10 - 확장 속성 패턴
- C# 11 - 목록 패턴
- C# 11 - 문자열 리터럴의 패턴 일치
Span<char>