← 문제로

33. ValueTuple vs Tuple: 값 의미론·가변성·상등·이름 소거

난이도 중
내 리뷰 · C#
해설 · C#

해설 — ValueTuple vs Tuple: 값 의미론·가변성·상등·이름 소거

난이도: 중

요약

출력:

A x=3 y=4
B a.hp=10 b.hp=999
C a=(10,42)
D True True
E True
F True False

(int, int) 구문은 **System.ValueTuple<...>(가변 struct, 값 형식)**이고, Tuple<...>불변 참조 형식이다. ValueTuple 은 복사 시 독립적이고 필드를 직접 수정할 수 있으며, 요소 이름은 컴파일 타임 장식이라 런타임 상등에 영향을 주지 않는다. 둘 다 Equals는 요소별 비교지만, ValueTuple 은 == 연산자까지 제공한다.

단계별 내부 동작

  1. (A): GetSpawn()(3,4) 반환, var (x,y)=... 는 분해(Deconstruct). → x=3 y=4.
  2. (B): var b = a;값 복사(struct). b.hp=999는 복사본만. → a.hp=10 b.hp=999.
  3. (C): ValueTuple 은 가변 structa.mp=42로 필드를 직접 바꿀 수 있다. → a=(10,42).
  4. (D): ValueTuple 은 ==/!= 연산자(C# 7.3+)와 Equals를 요소별로 정의 → 둘 다 True.
  5. (E): 요소 이름(first/second vs left/right)은 런타임 타입에 없다. named1, named2는 모두 ValueTuple<int,int>Equals가 요소별 비교로 True.
  6. (F): Tuple<int,int>Equals를 요소별로 오버라이드 → True. 하지만 서로 다른 힙 객체라 ReferenceEqualsFalse.

핵심 개념

  • ValueTuple: 값 형식(struct), public 가변 필드 Item1, Item2...(선택적 이름은 별칭). 스택/인라인 저장, 할당 없음. ==, Equals, GetHashCode, IComparable 요소별 구현. 분해(Deconstruct) 지원.
  • Tuple: 참조 형식(class), 불변(읽기 전용 프로퍼티 Item1...), 힙 할당. Equals는 요소별 오버라이드하지만 ==는 참조 비교(연산자 미제공).
  • 요소 이름 소거: (hp:int, mp:int)의 이름은 컴파일러가 TupleElementNamesAttribute 메타데이터로만 남기고 IL 타입은 ValueTuple<int,int>. 그래서 이름이 달라도 같은 타입·같은 상등.

흔한 오해·함정

  • "튜플은 불변" — Tuple 은 불변이지만 ValueTuple 은 가변이다. 필드를 실수로 바꿀 수 있어, 딕셔너리 키 등으로 쓸 때 주의(값이 바뀌면 해시 위치가 어긋남 — 다만 값 복사 특성상 컨테이너에 넣으면 복사본이 저장됨).
  • 박싱: ValueTuple 을 object/인터페이스로 담으면 박싱되어 힙 할당·복사 발생. 반복 경로에서 object로 올리지 말 것. Tuple 은 애초에 참조형이라 별도 박싱은 없지만 생성 시 힙 할당.
  • 성능: 다중 반환·임시 그룹핑엔 ValueTuple 이 할당이 없어 유리. 오래 보관·불변 계약이 중요하면 record/Tuple 고려.
  • 가변 struct 복사 함정: 프로퍼티가 ValueTuple 을 반환하면 그 반환값을 수정해도 원본에 반영되지 않는다(복사본 수정). ref return 이 아니면 제자리 수정 불가.

면접 포인트

  • (a,b) = ValueTuple(값형/가변), Tuple<>(참조형/불변)의 차이와 복사 독립성.
  • 요소 이름이 런타임에 소거되어(TupleElementNamesAttribute) 상등/타입에 영향 없음.
  • 박싱·힙 할당 관점의 성능 차이와 다중 반환 시 ValueTuple 선호 이유.