튜플(Visual Basic)

Visual Basic 2017부터 Visual Basic 언어는 튜플을 만들고 튜플 요소에 더 쉽게 액세스할 수 있도록 튜플에 대한 기본 지원을 제공합니다. 튜플은 특정 수와 값 시퀀스를 갖는 경량 데이터 구조체입니다. 튜플을 인스턴스화할 때 각 값(또는 요소)의 수와 데이터 형식을 정의합니다. 예를 들어, 2-튜플(또는 쌍)에는 두 개의 요소가 있습니다. 첫 번째는 Boolean 값일 수 있고 두 번째는 String일 수 있습니다. 튜플을 사용하면 단일 개체에 여러 값을 쉽게 저장할 수 있으므로 메서드에서 여러 값을 반환하는 간단한 방법으로 자주 사용됩니다.

Important

튜플을 지원하려면 ValueTuple 형식이 필요합니다. .NET Framework 4.7이 설치되지 않은 경우 NuGet 갤러리에서 사용할 수 있는 NuGet 패키지 System.ValueTuple을 추가해야 합니다. 이 패키지가 없으면 "미리 정의된 형식 "ValueTuple(Of,,,)"이 정의되지 않았거나 가져오지 않았습니다."와 유사한 컴파일 오류가 발생할 수 있습니다.

튜플 인스턴스화 및 사용

쉼표로 구분된 값을 괄호로 묶어 튜플을 인스턴스화합니다. 그런 다음 각 값은 튜플의 필드가 됩니다. 예를 들어, 다음 코드는 Date를 첫 번째 값으로, String을 두 번째 값으로, Boolean을 세 번째 값으로 사용하여 트리플(또는 3-튜플)을 정의합니다.

Dim holiday = (#07/04/2017#, "Independence Day", True)

기본적으로 튜플에 있는 각 필드의 이름은 튜플에서 필드의 1부터 시작하는 위치와 함께 문자열 Item으로 구성됩니다. 이 3-튜플의 경우 Date 필드는 Item1, String 필드는 Item2, Boolean 필드는 Item3입니다. 다음 예에서는 이전 코드 줄에서 인스턴스화된 튜플의 필드 값을 표시합니다.

Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' Output: 7/4/2017 12:00:00 AM Is Independence Day, a national holiday

Visual Basic 튜플의 필드는 읽기-쓰기입니다. 튜플을 인스턴스화한 후 해당 값을 수정할 수 있습니다. 다음 예에서는 이전 예에서 만들어진 튜플의 세 필드 중 두 개를 수정하고 결과를 표시합니다.

holiday.Item1 = #01/01/2018#
holiday.Item2 = "New Year's Day"
Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' Output: 1/1/2018 12:00:00 AM Is New Year's Day, a national holiday

명명된 튜플 인스턴스화 및 사용

튜플 필드에 기본 이름을 사용하는 대신 튜플 요소에 고유한 이름을 할당하여 이름이 할당된 튜플을 인스턴스화할 수 있습니다. 그런 다음 할당된 이름 또는 기본 이름으로 튜플의 필드에 액세스할 수 있습니다. 다음 예에서는 첫 번째 필드의 이름을 명시적으로 EventDate, 두 번째 Name 및 세 번째 IsHoliday로 지정한다는 점을 제외하고 이전과 동일한 3-튜플을 인스턴스화합니다. 그런 다음 필드 값을 표시하고 수정한 후 필드 값을 다시 표시합니다.

Dim holiday = (EventDate:=#07/04/2017#, Name:="Independence Day", IsHoliday:=True)
Console.WriteLine($"{holiday.EventDate} Is {holiday.Name}" +
                  $"{If(holiday.IsHoliday, ", a national holiday", String.Empty)}")
holiday.Item1 = #01/01/2018#
holiday.Item2 = "New Year's Day"
Console.WriteLine($"{holiday.Item1} is {holiday.Item2}" +
                  $"{If(holiday.Item3, ", a national holiday", String.Empty)}")
' The example displays the following output:
'   7/4/2017 12:00:00 AM Is Independence Day, a national holiday
'   1/1/2018 12:00:00 AM Is New Year's Day, a national holiday

변수, 필드 또는 매개 변수의 형식 선언의 일부로 튜플 이름을 지정할 수도 있습니다.

Dim holiday As (EventDate As Date, Name As String, IsHoliday As Boolean) =
    (#07/04/2017#, "Independence Day", True)
Console.WriteLine(holiday.Name)
' Output: Independence Day

또는 메서드의 반환 형식에 있습니다.

이는 컬렉션 이니셜라이저에 튜플을 제공할 때 특히 유용합니다. 튜플 이름은 컬렉션 형식 선언의 일부로 제공될 수 있습니다.

Dim events As New List(Of (EventDate As Date, Name As String, IsHoliday As Boolean)) From {
    (#07/04/2017#, "Independence Day", True),
    (#04/22/2017#, "Earth Day", False)
}
Console.WriteLine(events(1).IsHoliday)
' Output: False

유추된 튜플 요소 이름

Visual Basic 15.3부터 Visual Basic은 튜플 요소의 이름을 유추할 수 있습니다. 명시적으로 할당할 필요는 없습니다. 유추된 튜플 이름은 변수 집합에서 튜플을 초기화하고 튜플 요소 이름을 변수 이름과 동일하게 하려는 경우에 유용합니다.

다음 예에서는 명시적으로 이름이 지정된 세 가지 요소(state, stateNamecapital)를 포함하는 stateInfo 튜플을 만듭니다. 요소 이름을 할당할 때 튜플 초기화 문은 단순히 이름이 할당된 요소에 동일한 이름의 변수 값을 할당합니다.

Const state As String = "MI"
Const stateName As String = "Michigan"
Const capital As String = "Lansing"
Dim stateInfo = (state:=state, stateName:=stateName, capital:=capital)
Console.WriteLine($"{stateInfo.stateName}: 2-letter code: {stateInfo.state}, Capital {stateInfo.capital}")
' The example displays the following output:
'      Michigan: 2-letter code: MI, Capital Lansing

요소와 변수의 이름이 같기 때문에 Visual Basic 컴파일러는 다음 예와 같이 필드 이름을 유추할 수 있습니다.

Const state As String = "MI"
Const stateName As String = "Michigan"
Const capital As String = "Lansing"
Dim stateInfo = (state, stateName, capital)
Console.WriteLine($"{stateInfo.stateName}: 2-letter code: {stateInfo.State}, Capital {stateInfo.capital}")
' The example displays the following output:
'      Michigan: 2-letter code: MI, Capital Lansing

유추된 튜플 요소 이름을 사용하도록 설정하려면 Visual Basic 프로젝트(*.vbproj) 파일에서 사용할 Visual Basic 컴파일러 버전을 정의해야 합니다.

<PropertyGroup>
  <LangVersion>15.3</LangVersion>
</PropertyGroup>

버전 번호는 15.3부터 시작하는 Visual Basic 컴파일러의 모든 버전일 수 있습니다. 특정 컴파일러 버전을 하드 코딩하는 대신 "최신"을 LangVersion 값으로 지정하여 시스템에 설치된 Visual Basic 컴파일러의 최신 버전으로 컴파일할 수도 있습니다.

자세한 내용은 Visual Basic 언어 버전 설정을 참조하세요.

어떤 경우에는 Visual Basic 컴파일러가 후보 이름에서 튜플 요소 이름을 유추할 수 없으며 튜플 필드는 Item1, Item2 등과 같은 기본 이름을 통해서만 참조될 수 있습니다. 여기에는 다음이 포함됩니다.

  • 후보 이름은 Item3, Rest 또는 ToString과 같은 튜플 멤버의 이름과 동일합니다.

  • 후보 이름이 튜플에 중복됩니다.

필드 이름 유추가 실패하면 Visual Basic은 컴파일러 오류를 생성하지 않으며 런타임 시 예외도 throw되지 않습니다. 대신, 튜플 필드는 Item1Item2와 같이 미리 정의된 이름으로 참조되어야 합니다.

튜플과 구조체 비교

Visual Basic 튜플은 System.ValueTuple 제네릭 형식 중 하나의 인스턴스인 값 형식입니다. 예를 들어, 이전 예에서 정의된 holiday 튜플은 ValueTuple<T1,T2,T3> 구조체의 인스턴스입니다. 이는 데이터를 위한 경량 컨테이너로 설계되었습니다. 튜플은 여러 데이터 항목이 포함된 개체를 쉽게 만드는 것을 목표로 하기 때문에 사용자 지정 구조체에 있을 수 있는 일부 기능이 부족합니다. 여기에는 다음이 포함됩니다.

  • 사용자 지정 멤버. 튜플에 대해 고유한 속성, 메서드 또는 이벤트를 정의할 수 없습니다.

  • 확인 필드에 할당된 데이터의 유효성을 검사할 수 없습니다.

  • 불변성. Visual Basic 튜플은 변경 가능합니다. 대조적으로, 사용자 지정 구조체를 사용하면 인스턴스가 변경 가능한지 또는 변경이 불가능한인지 제어할 수 있습니다.

사용자 지정 멤버, 속성 및 필드 유효성 검사 또는 불변성이 중요한 경우 Visual Basic Structure 문을 사용하여 사용자 지정 값 형식을 정의해야 합니다.

Visual Basic 튜플은 해당 ValueTuple 형식의 멤버를 상속합니다. 해당 필드 외에도 다음과 같은 메서드가 포함됩니다.

메서드 설명
CompareTo 현재 튜플을 요소 수가 동일한 다른 튜플과 비교합니다.
같음 현재 튜플이 다른 튜플이나 개체와 같은지 여부를 확인합니다.
GetHashCode 현재 인스턴스의 해시 코드를 계산합니다.
ToString (Item1, Item2...) 형식을 취하는 이 튜플의 문자열 표현을 반환합니다. 여기서 Item1Item2는 튜플 필드의 값을 나타냅니다.

또한 ValueTuple 형식은 사용자 지정 비교자를 정의할 수 있는 IStructuralComparableIStructuralEquatable 인터페이스를 구현합니다.

할당 및 튜플

Visual Basic에서는 필드 수가 동일한 튜플 형식 간의 할당을 지원합니다. 다음 중 하나가 true인 경우 필드 형식을 변환할 수 있습니다.

  • 원본 및 대상 필드의 형식이 동일합니다.

  • 원본 형식에서 대상 유형으로의 확장(또는 암시적) 변환이 정의됩니다.

  • Option StrictOn이고, 원본 형식을 대상 유형으로 축소(또는 명시적으로) 변환하는 것이 정의됩니다. 원본 값이 대상 유형의 범위를 벗어나면 이 변환에서 예외가 throw될 수 있습니다.

다른 변환은 할당에 고려되지 않습니다. 튜플 형식 간에 허용되는 할당 종류를 살펴보겠습니다.

다음 예제에서 사용되는 이러한 변수를 살펴보세요.

' The number and field types of all these tuples are compatible. 
' The only difference Is the field names being used.
Dim unnamed = (42, "The meaning of life")
Dim anonymous = (16, "a perfect square")
Dim named = (Answer:=42, Message:="The meaning of life")
Dim differentNamed = (SecretConstant:=42, Label:="The meaning of life")

처음 두 변수인 unnamedanonymous에는 필드에 대해 지정된 의미 체계 이름이 없습니다. 해당 필드 이름은 기본값인 Item1Item2입니다. 마지막 두 변수인 nameddifferentName에는 의미 체계 필드 이름이 있습니다. 이러한 두 튜플의 필드 이름은 서로 다릅니다.

이러한 네 튜플에서 필드 수('인자 수'라고도 함)는 같으며, 해당 필드의 형식도 동일합니다. 따라서 다음 할당이 모든 작동합니다.

' Assign named to unnamed.
named = unnamed

' Despite the assignment, named still has fields that can be referred to as 'answer' and 'message'.
Console.WriteLine($"{named.Answer}, {named.Message}")
' Output:  42, The meaning of life

' Assign unnamed to anonymous.
anonymous = unnamed
' Because of the assignment, the value of the elements of anonymous changed.
Console.WriteLine($"{anonymous.Item1}, {anonymous.Item2}")
' Output:   42, The meaning of life

' Assign one named tuple to the other.
named = differentNamed
' The field names are Not assigned. 'named' still has 'answer' and 'message' fields.
Console.WriteLine($"{named.Answer}, {named.Message}")
' Output:   42, The meaning of life

튜플 이름은 할당되지 않습니다. 필드 값은 튜플의 필드 순서에 따라 할당됩니다.

마지막으로, named의 첫 번째 필드가 Integer이고 conversion의 첫 번째 필드가 Long인 경우에도 named 튜플을 conversion 튜플에 할당할 수 있습니다. IntegerLong으로 변환하는 것은 확대 변환이기 때문에 이 할당은 성공합니다.

' Assign an (Integer, String) tuple to a (Long, String) tuple (using implicit conversion).
Dim conversion As (Long, String) = named
Console.WriteLine($"{conversion.Item1} ({conversion.Item1.GetType().Name}), " +
                  $"{conversion.Item2} ({conversion.Item2.GetType().Name})")
' Output:      42 (Int64), The meaning of life (String)

필드 수가 다른 튜플은 할당할 수 없습니다.

' Does not compile.
' VB30311: Value of type '(Integer, Integer, Integer)' cannot be converted
'          to '(Answer As Integer, Message As String)'
var differentShape = (1, 2, 3)
named = differentShape

메서드 반환 값으로의 튜플

메서드는 단일 값만 반환할 수 있습니다. 하지만 메서드 호출을 통해 여러 값을 반환하려는 경우가 많습니다. 이 제한 사항을 해결하는 방법에는 여러 가지가 있습니다.

  • 속성이나 필드가 메서드에서 반환된 값을 나타내는 사용자 지정 클래스나 구조체를 만들 수 있습니다. 이는 헤비급 솔루션입니다. 메서드 호출에서 값을 검색하는 것이 유일한 목적인 사용자 지정 형식을 정의해야 합니다.

  • 메서드에서 단일 값을 반환하고 나머지 값을 메서드에 대한 참조로 전달하여 반환할 수 있습니다. 여기에는 변수를 인스턴스화하는 오버헤드가 포함되며 참조로 전달하는 변수의 값을 실수로 덮어쓸 위험이 있습니다.

  • 여러 반환 값을 검색하기 위한 간단한 솔루션을 제공하는 튜플을 사용할 수 있습니다.

예를 들어, .NET의 TryParse 메서드는 구문 분석 작업이 성공했는지 여부를 나타내는 Boolean 값을 반환합니다. 구문 분석 작업의 결과는 메서드에 대한 참조로 전달된 변수로 반환됩니다. 일반적으로 Int32.TryParse와 같은 구문 분석 메서드 호출은 다음과 같습니다.

Dim numericString As String = "123456"
Dim number As Integer
Dim result = Integer.TryParse(numericString, number)
Console.WriteLine($"{If(result, $"Success: {number:N0}", "Failure")}")
'      Output: Success: 123,456

자체 메서드에서 Int32.TryParse 메서드에 대한 호출을 래핑하면 구문 분석 작업에서 튜플을 반환할 수 있습니다. 다음 예에서 NumericLibrary.ParseIntegerInt32.TryParse 메서드를 호출하고 두 요소가 있는 명명된 튜플을 반환합니다.

Imports System.Globalization

Public Module NumericLibrary
    Public Function ParseInteger(value As String) As (Success As Boolean, Number As Integer)
        Dim number As Integer
        Return (Integer.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, number), number)
    End Function
End Module

그런 후 다음과 같은 코드를 사용하여 메서드를 호출할 수 있습니다.

Dim numericString As String = "123,456"
Dim result = ParseInteger(numericString)
Console.WriteLine($"{If(result.Success, $"Success: {result.Number:N0}", "Failure")}")
Console.ReadLine()
'      Output: Success: 123,456

Visual Basic 튜플 및 .NET Framework의 튜플

Visual Basic 튜플은 .NET Framework 4.7에 도입된 System.ValueTuple 제네릭 형식 중 하나의 인스턴스입니다. .NET Framework에는 제네릭 System.Tuple 클래스 집합도 포함되어 있습니다. 그러나 이러한 클래스는 여러 면에서 Visual Basic 튜플 및 System.ValueTuple 제네릭 형식과 다릅니다.

  • Tuple 클래스의 요소는 Item1, Item2 등으로 명명된 속성입니다. Visual Basic 튜플 및 ValueTuple 형식에서 튜플 요소는 필드입니다.

  • Tuple 인스턴스 또는 ValueTuple 인스턴스의 요소에는 의미 있는 이름을 할당할 수 없습니다. Visual Basic을 사용하면 필드의 의미를 전달하는 이름을 할당할 수 있습니다.

  • Tuple 인스턴스의 속성은 읽기 전용입니다. 튜플은 변경할 수 없습니다. Visual Basic 튜플 및 ValueTuple 형식에서 튜플 필드는 읽기-쓰기입니다. 튜플은 변경 가능합니다.

  • 제네릭 Tuple 형식은 참조 형식입니다. 이러한 Tuple 형식을 사용한다는 것은 개체를 할당한다는 의미입니다. 실행 부하 과다 경로에서는 이로 인해 애플리케이션 성능이 크게 영향을 받을 수 있습니다. Visual Basic 튜플과 ValueTuple 형식은 값 형식입니다.

TupleExtensions 클래스의 확장 메서드를 사용하면 Visual Basic 튜플과 .NET Tuple 개체 간 변환이 쉬워집니다. ToTuple 메서드는 Visual Basic 튜플을 .NET Tuple 개체로 변환하고, ToValueTuple 메서드는 .NET Tuple 개체를 Visual Basic 튜플로 변환합니다.

다음 예에서는 튜플을 만들고 이를 .NET Tuple 개체로 변환한 다음 다시 Visual Basic 튜플로 변환합니다. 그런 다음 이 튜플을 원래 튜플과 비교하여 동일한지 확인합니다.

Dim cityInfo = (name:="New York", area:=468.5, population:=8_550_405)
Console.WriteLine($"{cityInfo}, type {cityInfo.GetType().Name}")

' Convert the Visual Basic tuple to a .NET tuple.
Dim cityInfoT = TupleExtensions.ToTuple(cityInfo)
Console.WriteLine($"{cityInfoT}, type {cityInfoT.GetType().Name}")

' Convert the .NET tuple back to a Visual Basic tuple and ensure they are the same.
Dim cityInfo2 = TupleExtensions.ToValueTuple(cityInfoT)
Console.WriteLine($"{cityInfo2}, type {cityInfo2.GetType().Name}")
Console.WriteLine($"{NameOf(cityInfo)} = {NameOf(cityInfo2)}: {cityInfo.Equals(cityInfo2)}")

' The example displays the following output:
'       (New York, 468.5, 8550405), type ValueTuple`3
'       (New York, 468.5, 8550405), type Tuple`3
'       (New York, 468.5, 8550405), type ValueTuple`3
'       cityInfo = cityInfo2 :  True

참고 항목