카테고리 없음

[C#] switch 완전 정리 - 값 비교를 넘어 다양한 패턴과 실무 활용

J.Note 2025. 8. 13. 18:57

Unity 실무에서 오브젝트 타입에 따라 분기하거나, 값 범위·속성 조합으로 분기해야 할 때 switch는 가장 읽기 쉬운 선택지다.

C# 7부터 패턴 매칭이 도입되면서 switch는 단순 “값 비교”를 넘어 타입 검사, 속성 검사, 범위/논리 결합까지 지원한다.

 


1️⃣ 값 비교(기본)

• 상수 비교 중심

• C# 7 이전에도 가능

• 문자열, 정수, 열거형 등과 잘 맞음

var state = "Idle";

switch (state)
{
    case "Idle":
        // ...
        break;
    case "Run":
        // ...
        break;
    default:
        // ...
        break;
}
 

2️⃣ 패턴 매칭 (C# 7+)

▪️타입 패턴 (Type Pattern)

• case Car car: 는 “target이 Car 타입이면 car 변수에 바인딩” 의미

• target이 null이어도 예외가 발생하지 않음: 어떤 패턴에도 매칭되지 않아 default로 감

public interface IOnClick { }

public class Car     : MonoBehaviour, IOnClick { }
public class Tag     : MonoBehaviour, IOnClick { }
public class Process : MonoBehaviour, IOnClick { }

IOnClick target = /* ... */;

switch (target)
{
    case null:
        // 명시적으로 null 케이스 다루기
        break;
    case Car car:
        // car 로 안전 캐스팅 완료
        break;
    case Tag tag:
        break;
    case Process process:
        break;
    default:
        break;
}

 

▪️ when 가드

• 복잡한 if/else 체인을 없애고, 조건마다 분기 행동을 깔끔하게 분리

switch (target)
{
    case Car car when car.enabled:
        // 조건까지 만족할 때만
        break;
    case Car:
        // Car지만 위 조건을 만족하지 않을 때
        break;
    default:
        break;
}

 

 


3️⃣ 값을 return하는 간결한 표현 (C# 8+)

• 매핑/포맷에 특히 유용

가장 좋아하고 애용하는 패턴

string GetCode(string key) => key switch
{
    "aa" => "1",
    "bb" => "2",
    _    => ""     // 매칭 실패 시 기본값 (필수: 식은 ‘완전성’ 요구)
};

4️⃣ 관계 패턴·논리 패턴 (C# 9+)

• <, >, >=, <= 등의 관계 패턴

• and, or, not 같은 논리 패턴 조합

• 순서가 위에서부터 평가되므로 구체적인 조건을 먼저 두는 것이 안전

 

string ClassifySpeed(float v) => v switch
{
    < 0        => "Invalid",
    0          => "Stopped",
    > 0 and < 5 => "Slow",
    >= 5 and < 20 => "Cruise",
    >= 20     => "Fast",
    _         => "Unknown"
};

 


 

5️⃣속성/튜플/위치 패턴으로 struct에 매칭(C# 8/9+)

▪️속성(Property) 패턴

string Describe(Car car) => car switch
{
    { enabled: true,  tag: "Player" } => "Player Car",
    { enabled: false }                => "Disabled Car",
    _                                 => "Normal Car"
};

▪️튜플(Tuple) 패턴

(string state, int gear) key = ("Drive", 2);

var label = key switch
{
    ("Park",  _)        => "P",
    ("Drive", <= 2)     => "D-Low",
    ("Drive",  > 2)     => "D-High",
    ("Neutral", _)      => "N",
    ("Reverse", _)      => "R",
    _                   => "?"
};

▪️위치/분해 패턴 (Deconstruct 지원 타입)

// 예시: (x, y)로 분해 가능한 타입
var text = point switch
{
    (0, 0)            => "Origin",
    (>= 0, >= 0)      => "Q1",
    (<= 0, >= 0)      => "Q2",
    (<= 0, <= 0)      => "Q3",
    (>= 0, <= 0)      => "Q4",
    _                 => "Unknown"
};

 


 

💡 실무 팁

1.분기 순서

  • 구체적인 패턴을 위에, 넓은(포괄) 패턴을 아래로 적용

   예: Car c when c.enabled(맨 위) → Car → default(아래)

 

2. 가독성 우선

  • 동일 타입에서 조건만 다른 경우 when 가드로 분리하면 if-else 사슬보다 읽기 쉬움

 

3. switch 식 활용

  •  “입력 → 결과” 매핑은 3️⃣(값을 return)식이 가장 깔끔함

  •  Unity UI 라벨, 로그 텍스트, 상태 문자열 등에서 특히 효과적

 

4. 성능 관점

  •  단순 값 매핑/타입 분기에서 switch vs if-else 성능 차이는 미미

  •  읽기 쉽고 유지보수 쉬운 방식을 우선

 

5. 열거형 마지막 주의

  • enum 분기 시 가능한 값 전부를 나열해 컴파일러 경고/미스 방지

  • 필요 시  _ 나 default를 마지막에 배치.


예제 - 클릭 대상 타입별 처리

▪️보통 if / else 사용

void OnClicked(IOnClick target)
{
    if (target is Car car) 
    { /* ... */ 
    }
    else if (target is Tag tag) 
    { /* ... */ 
    }
    else if (target is Process process) 
    { /* ... */ 
    }
}

▪️개선 - if / else → switch(기본적)

void OnClicked(IOnClick target)
{
    switch (target)
    {
        case Car car when car.enabled:
            // 활성 Car만
            break;
        case Car:
            // 비활성 Car
            break;
...
        case null:
            // NRE 방지
            break;
        default:
            // 미래 타입 대비
            break;
    }
}

▪️추가 개선 - switch(기본적) → switch(Action 반환)

• 행동을 값(델리게이트) 로 만들어 반환
• 테스트/조합 용이, UI 이벤트 바인딩에도 깔끔.

Action ResolveAction(IOnClick t) => t switch
{
    Car     c when c.enabled => () => HandleActiveCar(c),
    Car                       => () => HandleInactiveCar((Car)t),
    Tag     tg                => () => HandleTag(tg),
    Process pr                => () => HandleProcess(pr),
    null                      => () => { /* ignore */ },
    _                         => () => { /* unknown */ }
};

void OnClicked(IOnClick target) => ResolveAction(target)();
 

 

📌결론

  • 단순 값 비교는 기본 switch, 매핑은 switch 식이 가장 깔끔.
  • 타입/속성/범위/논리 조합이 필요하면 패턴 매칭 switch 로 가독성과 유지보수성을 올릴 것.
  • null은 어떤 타입 패턴에도 매칭되지 않으므로 명시적으로 처리하거나 default에서 안전하게 마무리.
  • 성능은 대부분의 실무 케이스에서 가독성 좋은 코드가 우선이며, 필요한 경우에만 프로파일링.