서로 다른 타입의 값 여러 개를 “작은 묶음”으로 만들어서, 한 변수 / 한 반환값으로 다루게 해주는 타입이다.
예를 들어,
stringintdouble
이런 정보를 위해 Person 클래스를 굳이 만들지 않고, 그냥 한 번에 묶어서 쓰고 싶다면 튜플을 사용할 수 있다.
(string Name, int Age, double Height) person = ("홍길동", 30, 175.5);
이렇게 하면 person 하나에 세 가지 값이 함께 들어간다.
C#에는 크게 두 세대의 튜플이 있다.
Tuple<T1, T2, ...>Item1, Item2로 접근(int, string) 이런 식 문법, C# 7부터 본격 도입현재는 보통 2번 ValueTuple 문법을 쓰는 것이 일반적이다.
var t = (10, "Hello"); // (int, string) 타입으로 추론됨
타입을 직접 명시해도 된다.
(int, string) t = (10, "Hello");
Console.WriteLine(t.Item1); // 10
Console.WriteLine(t.Item2); // Hello
이름 없이 사용할 경우 Item1, Item2와 같은 기본 이름으로 접근할 수 있다.
튜플 요소에는 직접 이름을 붙일 수 있다. 이 방식을 많이 사용한다.
(string Name, int Age) person = ("홍길동", 30);
Console.WriteLine(person.Name); // "홍길동"
Console.WriteLine(person.Age); // 30
이렇게 쓰면 Item1, Item2 대신 의미 있는 이름으로 접근할 수 있어 코드 가독성이 좋아진다.
var와 함께 쓰는 경우도 흔하다.
var person = (Name: "홍길동", Age: 30);
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
C# 메서드는 원래 반환값을 하나만 돌려줄 수 있다. 그래서 예전에는 다음과 같은 방법을 많이 썼다.
out 매개변수ref 매개변수튜플을 쓰면 “반환값은 하나이지만, 그 안에 여러 개를 묶어서 보내는 것”이 가능해진다.
// 몫과 나머지를 한 번에 반환하는 메서드
static (int Quotient, int Remainder) Divide(int a, int b)
{
int q = a / b;
int r = a % b;
return (q, r); // 튜플로 반환
}
사용 예:
var result = Divide(17, 5);
Console.WriteLine(result.Quotient); // 3
Console.WriteLine(result.Remainder); // 2
튜플을 그대로 받지 않고, 변수 두 개로 바로 쪼개서 받을 수도 있다.
var (q, r) = Divide(17, 5); // 튜플을 두 변수로 분해
Console.WriteLine(q); // 3
Console.WriteLine(r); // 2
실무에서 자주 쓰는 패턴이다.
(T1, T2, ...) 형태의 튜플을 반환하고var (x, y) = ...; 형태로 깔끔하게 받는다LINQ를 사용할 때, “이 값과 저 값을 같이 들고 다니고 싶다”는 상황이 자주 나온다.
var numbers = new[] { 3, 5, 7, 10 };
// 숫자와 그 숫자의 제곱을 한꺼번에 묶어서 리스트로 만들기
var list = numbers
.Select(n => (Number: n, Square: n * n))
.ToList();
foreach (var item in list)
{
Console.WriteLine($"값: {item.Number}, 제곱: {item.Square}");
}
여기서 (Number: n, Square: n * n) 부분이 튜플이다.
클래스를 만들기 애매한 간단한 상황에서, “필드 2~3개짜리 임시 DTO 역할”로 자주 사용된다.
이 정도면 굳이 클래스를 만들긴 애매한데… 싶은 상황에서 튜플이 딱 맞다.
(string Id, string Name, bool IsActive) userInfo = ("U001", "홍길동", true);
Console.WriteLine(userInfo.Id);
Console.WriteLine(userInfo.Name);
Console.WriteLine(userInfo.IsActive);
예를 들어:
이럴 때 튜플은 아주 간편한 자료 구조가 된다.
반대로, 아래처럼 의미 있는 도메인 개념일 경우:
Order, Customer, Product, Invoice 등이런 것들은 별도의 클래스/레코드 타입을 만드는 편이 훨씬 좋다. 튜플은 말 그대로 “가벼운 묶음” 정도로 생각하면 된다.
예전에는 이런 방식의 튜플을 썼다.
var t = Tuple.Create(10, "Hi");
Console.WriteLine(t.Item1); // 10
Console.WriteLine(t.Item2); // Hi
Tuple<T1, T2> : 참조 타입(클래스)(int, string) : struct(값 타입)
값 튜플은 C# 7부터 본격 도입되었고, 문법이 훨씬 깔끔하며 성능도 더 좋다.
요즘에는 특별한 이유가 없다면 Tuple<...>보다는
(int, string) 같은 값 튜플 스타일을 쓰는 것이 일반적이다.
간단하게 다음 기준으로 생각하면 편하다.
(int Id, string Name) user = (1, "홍길동");
Console.WriteLine(user.Id);
Console.WriteLine(user.Name);
(int q, int r) Divide(int a, int b) => (a / b, a % b);
var (q, r) = Divide(17, 5);
튜플은 “클래스를 만들기엔 너무 무겁고, 값 하나로는 부족한 상황”에서 빛을 발한다.
가볍게 여러 값을 묶고 싶을 때, 먼저 떠올려볼 만한 도구라고 보면 된다.