22 5월 2025
현대의 C#은 조건부 논리를 처리하는 방식에 있어 조용한 혁명을 겪었습니다. 타입 확인과 값 비교를 위해 장황한 if-else
계단식 구조나 투박한 switch
문이 필요했던 시대는 지났습니다. 특히 C# 8.0 이후로 도입된 정교한 패턴 매칭 기능은 개발자가 제어 흐름을 작성하는 방식을 근본적으로 변화시켰습니다. 이는 코드를 동시에 더 표현력 있고, 더 간결하며, 더 안전하게 만듭니다.
C#의 패턴 매칭은 식을 테스트하고 해당 식이 특정 패턴과 일치할 때 작업을 수행하기 위한 간결한 구문을 제공합니다. 이를 통해 개발자는 값의 형식, 값, 속성 또는 복잡한 객체의 구조를 포함한 다양한 기준에 대해 값을 테스트할 수 있습니다. 이 기능은 주로 is
식과 switch
식 (또는 switch
문)을 통해 노출됩니다. 기본적인 형식 확인은 이전에 존재했지만, C# 8.0은 더 풍부한 패턴 어휘를 도입하여 일상적인 코딩 관행에 대한 유용성과 영향을 크게 확장했습니다. 이러한 개선은 효과적인 C# switch
패턴 매칭과 C# if
패턴 매칭에 중요하며, 장황한 조건부 논리를 간결하고 이해하기 쉬운 구조로 변환합니다.
몇 가지 주요 패턴 유형과 이러한 패턴이 일반적인 프로그래밍 작업을 어떻게 단순화하는지 살펴보며, 그 이점을 보여주는 명확한 C# 패턴 매칭 예시를 제공하겠습니다.
패턴 매칭의 진정한 힘은 전통적인 접근 방식과 비교할 때 분명해집니다. 개발자는 코드 가독성과 간결성에서 상당한 개선을 얻을 수 있습니다.
선언 패턴은 식의 런타임 형식을 확인하고, 일치하는 경우 결과를 새 변수에 할당합니다. 이는 많은 시나리오에서 명시적 형변환과 null
검사의 필요성을 제거합니다.
이전:
public void ProcessAssetOld(object asset)
{
if (asset is SoftwareLicense)
{
SoftwareLicense license = (SoftwareLicense)asset;
Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
}
else if (asset is HardwareComponent)
{
HardwareComponent hardware = (HardwareComponent)asset;
Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
}
else
{
Console.WriteLine("Unknown asset type.");
}
}
public class SoftwareLicense { public string ProductName { get; set; } public DateTime ExpirationDate { get; set; } }
public class HardwareComponent { public string ComponentName { get; set; } public string SerialNumber { get; set; } }
이후 (선언 패턴 사용):
public void ProcessAssetNew(object asset)
{
if (asset is SoftwareLicense license)
{
Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
}
else if (asset is HardwareComponent hardware)
{
Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
}
else
{
Console.WriteLine("Unknown asset type.");
}
}
license
및 hardware
변수는 패턴이 일치할 경우에만 범위 내에 있고 할당되므로 형식 안전성을 높이고 잠재적인 런타임 오류를 줄여줍니다.
선언 패턴과 유사하게, 형식 패턴은 식의 런타임 형식을 확인합니다. 이 패턴은 패턴 자체에 새 변수 선언이 엄격하게 필요하지 않은 switch
식 내에서 자주 사용됩니다.
이전:
public decimal CalculateShippingCostOld(Shipment shipment)
{
if (shipment == null)
throw new ArgumentNullException(nameof(shipment));
if (shipment.GetType() == typeof(StandardShipment))
return 5.00m;
if (shipment.GetType() == typeof(ExpressShipment))
return 15.00m;
if (shipment.GetType() == typeof(InternationalShipment))
return 25.00m;
return 0.00m; // Default for unknown types
}
public abstract class Shipment { }
public class StandardShipment : Shipment { }
public class ExpressShipment : Shipment { }
public class InternationalShipment : Shipment { }
이후 (switch
식에서 형식 패턴 사용):
public decimal CalculateShippingCostNew(Shipment shipment) => shipment switch
{
StandardShipment => 5.00m,
ExpressShipment => 15.00m,
InternationalShipment => 25.00m,
null => throw new ArgumentNullException(nameof(shipment)),
_ => 0.00m // Handle unknown shipment types
};
이는 다양한 형식에 대한 간결한 C# switch
case
패턴 매칭을 보여줍니다.
상수 패턴은 식의 결과가 지정된 상수 값과 동일한지 테스트합니다. 이는 개별 값 비교를 단순화합니다.
이전:
public string GetOrderStateOld(OrderStatus status)
{
if (status == OrderStatus.Pending)
return "Order is awaiting processing.";
else if (status == OrderStatus.Shipped)
return "Order has been dispatched.";
else if (status == OrderStatus.Delivered)
return "Order has been delivered.";
else if (status == OrderStatus.Cancelled)
return "Order has been cancelled.";
else
return "Unknown order state.";
}
public enum OrderStatus { Pending, Shipped, Delivered, Cancelled, Returned }
이후 (상수 패턴 사용):
public string GetOrderStateNew(OrderStatus status) => status switch
{
OrderStatus.Pending => "Order is awaiting processing.",
OrderStatus.Shipped => "Order has been dispatched.",
OrderStatus.Delivered => "Order has been delivered.",
OrderStatus.Cancelled => "Order has been cancelled.",
_ => "Unknown order state."
};
이는 특정 C# 문자열 패턴 매칭 또는 열거형 값을 처리하는 깔끔한 방법입니다.
관계형 패턴은 비교 연산자( <
, >
, <=
, >=
)를 사용하여 식의 결과를 상수와 비교합니다. 이는 범위 검사를 훨씬 더 읽기 쉽게 만듭니다.
이전:
public string GetEmployeePerformanceLevelOld(int score)
{
if (score < 50)
return "Needs Improvement";
else if (score >= 50 && score < 70)
return "Meets Expectations";
else if (score >= 70 && score < 90)
return "Exceeds Expectations";
else if (score >= 90)
return "Outstanding";
else
return "Invalid Score"; // Should not happen with int, but for completeness
}
이후 (관계형 패턴 사용):
public string GetEmployeePerformanceLevelNew(int score) => score switch
{
< 50 => "Needs Improvement",
>= 50 and < 70 => "Meets Expectations",
>= 70 and < 90 => "Exceeds Expectations",
>= 90 => "Outstanding",
_ => "Invalid Score" // Catches any unexpected integer values
};
이는 관계형 패턴과 논리형 패턴을 결합하여 C#에서 패턴 매칭을 수행하는 강력함을 보여줍니다.
속성 패턴은 식의 속성 또는 필드를 중첩 패턴과 일치시킬 수 있습니다. 이는 객체의 상태를 검사하는 데 매우 유용합니다.
이전:
public decimal CalculateOrderDiscountOld(CustomerOrder order)
{
if (order == null) return 0m;
if (order.TotalAmount >= 500 && order.Customer.IsPremium)
{
return 0.15m; // 15% for premium customers with large orders
}
else if (order.TotalAmount >= 100)
{
return 0.05m; // 5% for general large orders
}
return 0m;
}
public class Customer { public bool IsPremium { get; set; } }
public class CustomerOrder { public decimal TotalAmount { get; set; } public Customer Customer { get; set; } }
이후 (속성 패턴 사용):
public decimal CalculateOrderDiscountNew(CustomerOrder order) => order switch
{
{ TotalAmount: >= 500, Customer.IsPremium: true } => 0.15m,
{ TotalAmount: >= 100 } => 0.05m,
null => 0m,
_ => 0m
};
이는 강력한 C# 속성 패턴 매칭을 보여주며, 객체 상태 검사를 단순화합니다.
위치 패턴은 식의 결과(예: Deconstruct
메서드가 있는 튜플 또는 레코드)를 분해하고, 결과 값을 중첩 패턴과 일치시킵니다.
이전:
public string DescribeTransactionOld(Transaction transaction)
{
if (transaction.Type == TransactionType.Deposit && transaction.Amount > 1000)
return "Large Deposit";
else if (transaction.Type == TransactionType.Withdrawal && transaction.Amount > 500)
return "Significant Withdrawal";
else if (transaction.Type == TransactionType.Fee && transaction.Amount > 0)
return "Applied Fee";
else
return "Standard Transaction";
}
public record Transaction(TransactionType Type, decimal Amount);
public enum TransactionType { Deposit, Withdrawal, Fee, Transfer }
이후 (위치 패턴 사용):
public string DescribeTransactionNew(Transaction transaction) => transaction switch
{
(TransactionType.Deposit, > 1000m) => "Large Deposit",
(TransactionType.Withdrawal, > 500m) => "Significant Withdrawal",
(TransactionType.Fee, > 0m) => "Applied Fee",
_ => "Standard Transaction"
};
이 간결한 형식은 분해 가능한 형식에 대한 C# 패턴 매칭의 가독성을 높입니다.
and
, or
, not
)논리 패턴은 and
, or
, not
키워드를 사용하여 다른 패턴을 결합함으로써 단일 패턴 내에서 복잡한 조건부 검사를 가능하게 합니다.
이전:
public bool CanGrantAccessOld(UserRole role, int securityLevel, bool hasTwoFactorAuth)
{
if ((role == UserRole.Administrator || role == UserRole.Manager) && securityLevel > 7 && hasTwoFactorAuth)
return true;
else if (role == UserRole.Developer && securityLevel > 5)
return true;
else if (role != UserRole.Guest && securityLevel >= 3 && securityLevel <= 10)
return true;
else
return false;
}
public enum UserRole { Guest, User, Developer, Manager, Administrator }
이후 (논리 패턴 사용):
public bool CanGrantAccessNew(UserRole role, int securityLevel, bool hasTwoFactorAuth) => (role, securityLevel, hasTwoFactorAuth) switch
{
(UserRole.Administrator or UserRole.Manager, > 7, true) => true,
(UserRole.Developer, > 5, _) => true,
(not UserRole.Guest, >= 3 and <= 10, _) => true,
_ => false
};
논리 연산자와 결합된 C# is
패턴 매칭은 매우 표현력이 풍부해집니다.
var
패턴은 항상 식과 일치하고 해당 결과를 새 지역 변수에 할당합니다. 특히 when
절 내에서 메서드 호출 또는 다른 패턴으로 직접 표현할 수 없는 속성을 포함하는 추가적이고 더 복잡한 검사를 수행하기 위해 switch
식의 전체 입력 값(또는 복합 패턴의 일부)을 캡처해야 할 때 유용합니다.
이전:
using System.Collections.Generic;
using System.Linq;
public string AnalyzeNumberSequenceOld(IEnumerable<int> numbers)
{
if (numbers == null)
return "Invalid sequence (null)";
if (numbers.Count() == 0)
return "Empty sequence";
if (numbers.Count() > 5 && numbers.Average() > 10.0)
return "Long sequence with high average";
return "Normal sequence";
}
이후 (when
절과 var 패턴 사용):
using System.Collections.Generic;
using System.Linq;
public string AnalyzeNumberSequenceNew(IEnumerable<int> numbers) => numbers switch
{
null => "Invalid sequence (null)",
var list when list.Count() == 0 => "Empty sequence",
var list when list.Count() > 5 && list.Average() > 10.0 => "Long sequence with high average",
_ => "Normal sequence"
};
여기서 var list
는 IEnumerable<int>
인스턴스를 캡처합니다. 이를 통해 when
절 내에서 list
변수에 대해 Count()
및 Average()
와 같은 복잡한 LINQ 쿼리를 is
를 사용한 명시적 형식 확인 없이 직접 수행할 수 있습니다.
C# 11에 도입된 리스트 패턴은 시퀀스(배열 또는 List<T>
와 같은)의 요소와 일치시킬 수 있습니다. 이들은 컬렉션의 구조와 내용을 유효성 검사하는 데 매우 강력합니다.
이전:
public bool ValidateLogEntryOld(string[] logParts)
{
if (logParts == null || logParts.Length < 2) return false;
if (logParts[0] == "ERROR" && logParts.Length >= 3 && logParts[1] == "AUTH" && logParts[2] == "FAILED")
return true;
if (logParts[0] == "INFO" && logParts.Length >= 2 && logParts[1] == "CONNECTED")
return true;
if (logParts[0] == "WARN" && logParts.Length >= 4 && logParts[1] == "DISK" && logParts[2] == "SPACE" && int.TryParse(logParts[3], out int space) && space < 10)
return true;
return false;
}
이후 (리스트 패턴 사용):
public bool ValidateLogEntryNew(string[] logParts) => logParts switch
{
["ERROR", "AUTH", "FAILED", ..] => true, // 오류 인증 실패
["INFO", "CONNECTED", ..] => true, // 클라이언트 연결됨
["WARN", "DISK", "SPACE", var size, ..] when int.TryParse(size, out int space) && space < 10 => true, // 낮은 디스크 공간
_ => false // 기타 로그 항목
};
이는 시퀀스에 대한 패턴 매칭을 위한 강력한 기능으로, 특히 명령줄 인수 구문 분석 또는 다양한 구조를 가진 로그 항목과 같은 시나리오에서 유용합니다. ..
(슬라이스 패턴)은 0개 이상의 요소를 일치시키므로 유연성을 제공합니다.
패턴 매칭의 이점은 단순한 문법적 설탕을 넘어섭니다. 이러한 기능을 채택함으로써 개발자는 본질적으로 다음과 같은 코드를 작성하게 됩니다.
if-else
블록과 명시적 형변환을 제거함으로써 코드 양이 줄어들어 유지 관리할 코드가 적어지고 오류 발생 가능성이 줄어듭니다.switch
식은 종종 포괄적인 패턴 매칭을 요구합니다. C# 컴파일러는 switch
식이 모든 가능한 입력 값을 커버하지 않을 경우 개발자에게 경고하여 런타임 예외를 방지하는 데 도움을 줍니다. 이러한 컴파일러가 강제하는 포괄성은 소프트웨어의 정확성을 향상시킵니다.패턴 매칭은 C#을 복잡한 데이터 처리 및 제어 흐름에 대한 우아한 솔루션을 제공하는 언어로 변화시켰습니다. 기본적인 형식 확인부터 객체 분해 및 시퀀스 유효성 검사에 이르기까지, C# 8.0 이상에서 제공되는 기능은 모든 현대 소프트웨어 개발자에게 필수적인 도구입니다. 이러한 강력한 기능을 수용하는 것은 코드 품질을 향상시킬 뿐만 아니라 개발 프로세스를 간소화하여 더 안정적이고 유지 관리가 용이한 애플리케이션에 기여합니다. 여전히 이전 스타일의 if-else
또는 switch
문에 의존하는 개발자는 이러한 패턴을 통합함으로써 C# 프로그램을 더욱 견고하고 작업하기 즐겁게 만들 수 있습니다.