튜플 형식(C# 참조)

튜플 기능은 경량 데이터 구조에서 여러 데이터 요소를 그룹화하는 간결한 구문을 제공합니다. 다음 예제에서는 튜플 변수를 선언하고 초기화하며 관련 데이터 멤버에 액세스하는 방법을 보여 줍니다.

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

앞의 예제와 같이 튜플 형식을 정의하려면 모든 관련 데이터 멤버의 형식과 필요한 경우 필드 이름을 지정합니다. 튜플 형식에서 메서드를 정의할 수는 없지만 다음 예제와 같이 .NET에서 제공하는 메서드를 사용할 수 있습니다.

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

튜플 형식은 같음 연산자==!=(을)를 지원합니다. 자세한 내용은 튜플 같음 섹션을 참조하세요.

튜플 형식은 값 형식이며 튜플 요소는 공용 필드입니다. 이에 따라 튜플은 ‘변경 가능한’ 값 형식으로 설정됩니다.

임의의 많은 요소를 포함하는 튜플을 정의할 수 있습니다.

var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26);  // output: 26

튜플 사용 사례

튜플의 가장 일반적인 사용 사례 중 하나는 메서드 반환 형식입니다. 즉, out 메서드 매개 변수를 정의하는 대신 다음 예제와 같이 메서드 결과를 튜플 반환 형식으로 그룹화할 수 있습니다.

int[] xs = [4, 7, 9];
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

int[] ys = [-9, 0, 67, 100];
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)
{
    if (input is null || input.Length == 0)
    {
        throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
    }

    // Initialize min to MaxValue so every value in the input
    // is less than this initial value.
    var min = int.MaxValue;
    // Initialize max to MinValue so every value in the input
    // is greater than this initial value.
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

앞의 예제와 같이 반환된 튜플 인스턴스를 직접 사용하거나 개별 변수로 분해할 수 있습니다.

익명 형식 대신 튜플 형식을 사용할 수도 있습니다(예: LINQ 쿼리에서). 자세한 내용은 무명 형식과 튜플 형식 중에서 선택을 참조하세요.

일반적으로 튜플을 사용하여 관련 데이터 요소를 느슨하게 그룹화합니다. 공용 API에서 클래스 또는 구조체 형식을 정의하는 것이 좋습니다.

튜플 필드 이름

다음 예제와 같이 튜플 초기화 식 또는 튜플 형식 정의에서 튜플 필드 이름을 명시적으로 지정합니다.

var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

필드 이름을 지정하지 않으면 다음 예제와 같이 튜플 초기화 식의 해당 변수 이름에서 유추될 수 있습니다.

var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

이를 튜플 프로젝션 이니셜라이저라고 합니다. 다음과 같은 경우에 변수 이름은 튜플 필드 이름으로 프로젝션되지 않습니다.

  • 후보 이름이 튜플 형식의 멤버 이름인 경우(예: Item3, ToString 또는 Rest)
  • 후보 이름이 명시적이든 암시적이든 다른 튜플 필드 이름과 중복되는 경우

앞의 경우 필드의 이름을 명시적으로 지정하거나 기본 이름으로 필드에 액세스합니다.

튜플 필드의 기본 이름은 Item1, Item2, Item3 등입니다. 다음 예제와 같이 필드 이름이 명시적으로 지정되거나 유추되는 경우에도 언제든지 필드의 기본 이름을 사용할 수 있습니다.

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

튜플 할당튜플 같음 비교에서는 필드 이름을 고려하지 않습니다.

컴파일 시간에 컴파일러는 기본값이 아닌 필드 이름을 해당하는 기본 이름으로 바꿉니다. 따라서 명시적으로 지정되거나 유추된 필드 이름을 런타임에 사용할 수 없습니다.

.NET 코드 스타일 규칙 IDE0037을 사용하도록 설정하여 유추 또는 명시적 튜플 필드 이름에 대한 기본 설정을 지정합니다.

C# 12부터는 using 지시문을 사용하여 튜플 형식에 대한 별칭을 지정할 수 있습니다. 다음 예제에서는 허용되는 MinMax 값에 대해 두 개의 정수 값이 있는 튜플 형식에 대한 global using 별칭을 추가합니다.

global using BandPass = (int Min, int Max);

별칭을 선언한 후 BandPass 이름을 해당 튜플 형식의 별칭으로 사용할 수 있습니다.

BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");

별칭은 새 형식을 도입하지 않고 기존 형식의 동의어만 만듭니다. 기본 튜플 형식과 마찬가지로 BandPass 별칭으로 선언된 튜플을 분해할 수 있습니다.

(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");

튜플 할당 또는 분해와 마찬가지로 튜플 멤버 이름은 일치시킬 필요가 없습니다. 형식은 일치시켜야 합니다.

마찬가지로, 동일한 별칭과 멤버 형식을 가진 두 번째 별칭을 원래 별칭과 교환하여 사용할 수 있습니다. 두 번째 별칭을 선언할 수 있습니다.

using Range = (int Minimum, int Maximum);

BandPass 튜플에 Range 튜플을 할당할 수 있습니다. 모든 튜플 할당과 마찬가지로 필드 이름은 일치할 필요가 없으며 형식 및 결과만 일치시킬 필요가 없습니다.

Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");

튜플 형식의 별칭은 튜플을 사용할 때 더 많은 의미 체계 정보를 제공합니다. 새 형식을 도입하지는 않습니다. 형식 안전성을 제공하려면 위치 record(을)를 대신 선언해야 합니다.

튜플 할당 및 분해

C#은 다음 두 조건을 모두 충족하는 튜플 형식 간에 할당을 지원합니다.

  • 두 튜플 형식의 요소 수가 동일함
  • 각 튜플 위치에서 오른쪽 튜플 요소의 형식이 해당하는 왼쪽 튜플 요소의 형식과 동일하거나 해당 형식으로 암시적으로 변환 가능함

튜플 요소 값은 튜플 요소의 순서에 따라 할당됩니다. 다음 예제와 같이 튜플 필드의 이름은 무시되고 할당되지 않습니다.

(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14

(double A, double B) t3 = (2.0, 3.0);
t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14

= 대입 연산자를 사용하여 튜플 인스턴스를 개별 변수로 ‘분해’할 수도 있습니다. 여러 가지 방법으로 이 작업을 수행할 수 있습니다.

  • 괄호 밖에서 var 키워드를 사용하여 형식화된 변수를 암시적으로 선언하며 컴파일러가 해당 형식을 유추하도록 합니다.

    var t = ("post office", 3.6);
    var (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • 괄호 안에 각 변수의 형식을 명시적으로 선언합니다.

    var t = ("post office", 3.6);
    (string destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • 일부 형식을 명시적으로 선언하고 괄호 안에 암시적으로(var 포함) 다른 형식을 선언합니다.

    var t = ("post office", 3.6);
    (var destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • 기존 변수를 사용합니다.

    var destination = string.Empty;
    var distance = 0.0;
    
    var t = ("post office", 3.6);
    (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    

분해 식의 대상에는 기존 변수와 분해 선언에 선언된 변수가 모두 포함될 수 있습니다.

분해를 패턴 일치와 결합하여 튜플의 필드 특성을 검사할 수도 있습니다. 다음 예제에서는 여러 정수로 반복하고 3으로 나눌 수 있는 정수로 인쇄합니다. Int32.DivRem의 튜플 결과를 분해하고 0의 Remainder(와)과 일치합니다.

for (int i = 4; i < 20;  i++)
{
    if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 ))
    {
        Console.WriteLine($"{i} is divisible by 3, with quotient {q}");
    }
}

튜플 및 기타 형식을 분해하는 방법에 관한 자세한 내용은 튜플 및 기타 형식 분해를 참조하세요.

튜플 같음

튜플 형식은 ==!= 연산자를 지원합니다. 해당 연산자는 튜플 요소 순서에 따라 왼쪽 피연산자의 멤버를 오른쪽 피연산자의 해당 멤버와 비교합니다.

(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);  // output: True
Console.WriteLine(left != right);  // output: False

var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);  // output: True
Console.WriteLine(t1 != t2);  // output: False

앞의 예제와 같이 ==!= 연산에는 튜플 필드 이름이 고려되지 않습니다.

다음 조건이 둘 다 충족되면 두 튜플을 비교할 수 있습니다.

  • 두 튜플의 요소 수가 동일합니다. 예를 들어 t1t2의 요소 수가 다른 경우 t1 != t2는 컴파일되지 않습니다.
  • 각 튜플 위치에서 왼쪽 및 오른쪽 튜플 피연산자의 해당 요소는 ==!= 연산자와 비교할 수 있습니다. 예를 들어 1(은)는 (1, 2)(와)과 비교할 수 없으므로 (1, (2, 3)) == ((1, 2), 3)(은)는 컴파일되지 않습니다.

==!= 연산자는 단락(short-circuiting) 방식으로 튜플을 비교합니다. 즉, 같지 않은 요소 쌍을 충족하거나 튜플의 끝에 도달하는 즉시 연산이 중지됩니다. 그러나 다음 예제와 같이 비교하기 전에 ‘모든’ 튜플 요소가 평가됩니다.

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
    Console.WriteLine(s);
    return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

출력 매개 변수 튜플

일반적으로 out 매개 변수를 포함하는 메서드는 튜플을 반환하는 메서드로 리팩터링합니다. 그러나 out 매개 변수가 튜플 형식일 수 있는 경우가 있습니다. 다음 예제에서는 튜플을 out 매개 변수로 사용하는 방법을 보여 줍니다.

var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
    Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20

튜플과 System.Tuple 비교

System.ValueTuple 형식으로 지원되는 C# 튜플은 System.Tuple 형식으로 표현되는 튜플과 다릅니다. 주요 차이점은 다음과 같습니다.

  • System.ValueTuple 형식은 값 형식입니다. System.Tuple 형식은 참조 형식입니다.
  • System.ValueTuple 형식은 변경할 수 있습니다. System.Tuple 형식은 변경할 수 없습니다.
  • System.ValueTuple 형식의 데이터 멤버는 필드입니다. System.Tuple 형식의 데이터 멤버는 속성입니다.

C# 언어 사양

자세한 내용은 다음을 참조하세요.

참고 항목