52. string 에 대한 switch 문의 컴파일러 변환 (해시 점프 vs 순차 비교)
난이도 중내 리뷰 · C#
해설 · C#
해설 — string 에 대한 switch 문의 컴파일러 변환 (해시 점프 vs 순차 비교)
난이도: 중
요약
C# 컴파일러는 string switch를 케이스 수에 따라 두 전략으로 낮춘다. 케이스가 적으면(대략 6개 이하) 단순한 if/else 순차 문자열 비교(==, 서수 비교) 체인으로, 케이스가 많으면 각 문자열의 해시를 계산해 그 해시값에 대한 정수 switch(점프 테이블) 로 분기한 뒤 각 후보에서 서수 == 로 최종 확인한다. 문자열 비교는 항상 서수(ordinal) 기준이라 "ROUTE"와 "route"는 서로 다른 케이스다(문화권/대소문자 무시가 아님).
핵심 개념
- string은 정수처럼 곧바로 점프 테이블을 만들 수 없다(값이 연속이 아님). 그래서:
- 소수 케이스:
if (op == "move") ... else if (op == "attack") .... 비교 횟수 최악 O(n). - 다수 케이스: 문자열 해시 → 해시값으로 정수 switch(점프 테이블) → 해당 후보에서 서수
==재확인. 실질 O(1)에 가깝다.
- 소수 케이스:
- 문자열 동등 비교는
string.Equals의 서수 의미(코드 유닛 단위 정확 일치). 대소문자·문화권 규칙을 타지 않는다.
단계별 내부 동작 (RouteMany, Roslyn 기준)
- 컴파일러가
<PrivateImplementationDetails>에ComputeStringHash헬퍼(비암호화 FNV류 해시)를 생성한다. 런타임Dictionary인스턴스를 만드는 게 아니다 — 해시 계산 + 정수 switch로 낮춘다. (참고: 옛 pre-Roslyncsc는 케이스가 많을 때 정적 필드에Dictionary<string,int>를 지연 생성하는 방식이었다. 현대 Roslyn은 해시-스위치 방식으로 바뀌었다.) - 런타임:
op의 해시를ComputeStringHash로 계산. - 그 uint 해시값에 대해 IL
switch(점프 테이블) 로 분기해 후보 케이스 그룹으로 이동. - 각 후보에서
op == "move"처럼 서수 문자열 비교로 최종 확인(해시 충돌·우연한 일치를 걸러냄). - 어디에도 안 맞으면
default(여기선 0).
RouteFew는 해시 없이 op == "move" / op == "attack" 두 번의 서수 비교 후 default.
흔한 오해·함정
- "string switch는 대소문자를 무시한다" — 아니다. 서수 비교라 정확히 일치해야 한다. 대소문자 무시가 필요하면
op.ToLowerInvariant()로 정규화 후 switch(단 ToLower는 할당 발생)하거나 명시 비교를 쓴다. - "내부적으로 Dictionary를 만든다" — 현대 Roslyn은 해시-스위치로 낮추며 런타임 사전을 할당하지 않는다(옛 컴파일러의 동작과 혼동 주의).
- 문화권 의존 걱정 — string switch는 서수라 문화권 불변이다(터키어 'I' 문제에서 자유). 반대로 "ß"↔"ss" 같은 문화권 동등은 매칭되지 않는다.
- null 입력: string switch는 null을 정상 처리한다.
null케이스가 있으면 그 팔로, 없으면default로 간다(NRE 아님). - 성능: 초고빈도 디스패치라면 해시+비교 비용을 감안해 정수 opcode(enum)로 바꿔 순수 정수 switch를 쓰는 편이 빠르다.
면접 포인트
- string switch의 두 lowering 전략(순차 비교 vs 해시-스위치)과 임계 케이스 수.
- 비교가 서수(ordinal)라는 점, 대소문자/문화권/null 처리.
- 현대 Roslyn이 런타임 Dictionary가 아니라
ComputeStringHash+정수 switch로 낮춘다는 사실.