튜플(Tuple)은 C# 7.0에서 추가된 기능이다. 이제 튜플을 통해서 두 개 이상의 값을 반환하는 메서드를 보다 깔끔하게, 효율적으로 구현할 수 있게 되었다. 기존에는 메소드에서 두 개 이상의 값을 반환하고 싶을 때는 아래와 같은 방법들을 이용했다.

(1) out 매개변수 한정자를 통해서 여러 개의 값을 넘겨주는 방법 (비동기 메서드에서는 사용할 수 없음)

(2) System.Tuple<...> 형식을 통한 튜플 객체 할당 (튜플 객체를 따로 할당해야 하는 것도 그렇지만 코드가 길게 늘어짐)

(3) 두 개 이상의 값을 반환해야 하는 메소드마다 전달을 위한 클래스/구조체를 따로 만들기 (필요 이상으로 코드 오버헤드가 생기게 됨)

(4) dynamic 반환형을 통해 익명 타입(Anonymous types)의 객체를 반환하는 방법 (정적 자료형 검사 X, 오버헤드가 상당함)

 

튜플의 사용 예시

아래와 같이 소괄호로 묶고 각 요소를 쉼표로 구분해주면 된다. 각 요소의 값들은 letters.Item1, letters.Item2, letters.Item3 필드를 통해 접근할 수 있다.

var letters = ("a", "b", "c");

각 요소에 자동으로 붙여지는 이름(Item1, Item2, ...)을 쓰고싶지 않다면, 아래와 같이 직접 사용자가 이름을 붙여줄 수도 있다. 이 경우에는 namedLetters.Alpha, namedLetters.Beta, namedLetters.Gamma로 각 요소에 접근할 수 있다.

(string Alpha, string Beta, string Gamma) namedLetters = ("a", "b", "c");

할당 연산자의 오른쪽에서 이름을 붙여줄 수도 있다.

var namedLetters = (Alpha: "a", Beta: "b", Gamma: "c");

할당 연산자의 왼쪽, 오른쪽에서 모두 이름을 지정해줄 수 있지만, 이름 Alpha와 Beta가 할당 연산자의 왼쪽에 있는 이름 First, Second와 충돌하기 때문에 이름 Alpha, Beta는 무시된다. 따라서, 아래와 같은 경우는 firstLetters.First, firstLetters.Second로만 접근이 가능하다.

(string First, string Second) firstLetters = (Alpha: "a", Beta: "b");

이번에는 메서드 정의에서 튜플이 어떻게 활용되는지 보도록 하자.

private static (int Max, int Min) Range(IEnumerable numbers)
{
    int min = int.MaxValue;
    int max = int.MinValue;
    foreach(var n in numbers)
    {
        min = (n < min) ? n : min;
        max = (n > max) ? n : max;
    }
    return (max, min);
}

소괄호 안에 (int, int)와 같이 여러 개의 반환형을 그대로 늘어놓기만 하면 된다. 위의 예제에서는 필드에 각각 Max와 Min이라는 이름을 지정해줬는데, 별도로 지정하지 않은 경우에는 필드 range.Item1, range.Item2로 접근할 수 있다. 

var range = Range(numbers);
Console.WriteLine($"Max: {range.Max}, Min: {range.Min}");
아래와 같이 튜플의 각 요소를 개별값으로 분해할 수도 있다.
(int max, int min) = Range(numbers);
Console.WriteLine($"Max: {max}, Min: {min}");

  '컴파일러에서 요구하는 'System.Runtime.CompilerServices.TupleElementNamesAttribute' 형식을 찾지 못했기 때문에 튜플을 사용하는 클래스 또는 멤버를 정의할 수 없습니다. 참조가 있는지 확인하세요.'와 같은 오류가 발생합니다. 어떻게 해야 하나요?