숏컷 코드
// 이름 있는 튜플 반환
(string Name, int Age) GetUser()
=> ("Mina", 26);
var user = GetUser();
Console.WriteLine(user.Name); // "Mina"
// 구조 분해 (deconstruction)
var (name, age) = GetUser();
Console.WriteLine($"{name} / {age}");문법
ValueTuple vs Tuple
C# 7 이전의 Tuple<T1, T2>는 클래스(참조 타입) 이고 Item1, Item2처럼 의미 없는 이름만 있습니다. C# 7+의 (T1, T2) 리터럴은 ValueTuple(값 타입) 으로 컴파일되고, 필드에 이름을 붙일 수 있습니다.
// 구식 Tuple — 힙 할당, Item1/Item2만 있음
Tuple<string, int> old = Tuple.Create("Mina", 26);
string name = old.Item1;
// 현대 ValueTuple — 스택 할당 가능, 이름 사용
(string Name, int Age) modern = ("Mina", 26);
string name = modern.Name;이름은 컴파일 타임 정보로만 존재합니다. 런타임에는 여전히 Item1, Item2로 접근하므로 리플렉션이나 직렬화에서는 이름이 보이지 않습니다.
여러 값을 반환할 때
메서드에서 두세 개 값을 묶어 반환하는 가장 가벼운 방법입니다.
(bool Success, string Error) Validate(string email)
{
if (!email.Contains('@'))
return (false, "@ 없음");
return (true, "");
}
var (ok, err) = Validate(input);
if (!ok) Console.WriteLine(err);커스텀 Deconstruct
어떤 타입이든 Deconstruct 메서드를 추가하면 var (a, b) = obj; 문법을 쓸 수 있습니다. extension method로도 추가 가능합니다.
public class Point
{
public double X { get; }
public double Y { get; }
public void Deconstruct(out double x, out double y)
=> (x, y) = (X, Y);
}
var p = new Point(3, 4);
var (x, y) = p; // Deconstruct 자동 호출튜플 vs record 선택 기준
| tuple | record | |
|---|---|---|
| 명시적 타입 이름 | 없음 | 있음 (User, Point) |
| 직렬화 | 이름 손실 | 유지됨 |
| 메서드 추가 | 불가 | 가능 |
| 공개 API 반환 타입 | ❌ 권장하지 않음 | ✅ |
| 임시 내부 묶음 | ✅ 간결함 | 과할 수 있음 |
체크포인트
| 문법 | 의미 |
|---|---|
(int X, int Y) | 이름 있는 ValueTuple 타입 |
var (x, y) = value | 구조 분해 대입 |
(_, int y) = value | 필요 없는 값은 _ 무시 |
return (true, "ok") | 여러 값 반환 |
void Deconstruct(out T a, ...) | 커스텀 분해 지원 |
주의할 점
의미가 복잡해지거나 타입을 재사용해야 한다면 record나 클래스로 바꾸는 편이 좋습니다. 특히 공개 API의 반환 타입으로 튜플을 쓰면 호출하는 쪽에서 Item1, Item2로 접근해야 할 수 있어 의미가 흐려집니다.
_ 무시 패턴은 필요 없는 값을 명시적으로 건너뛸 때 씁니다. (_, var y) = GetPoint()처럼 특정 위치만 받을 수 있습니다.
참고 링크
2 sources