Поделиться через


Кортежи (Visual Basic)

Начиная с Visual Basic 2017 язык Visual Basic предлагает встроенную поддержку кортежей, что упрощает создание кортежей и доступ к элементам кортежей. Кортеж — это упрощенная структура данных, которая имеет определенное число и последовательность значений. При создании экземпляра кортежа вы определяете количество и тип данных для каждого значения (или элемента). Например, 2-кортеж (или пара) содержит два элемента. Первое может быть значением Boolean , а второй — Stringзначением. Так как кортежи упрощают хранение нескольких значений в одном объекте, они часто используются в качестве упрощенного способа возврата нескольких значений из метода.

Внимание

Для поддержки кортежей требуется тип ValueTuple. Если платформа .NET Framework 4.7 не установлена, необходимо добавить пакет System.ValueTupleNuGet, который доступен в каталоге NuGet. Без этого пакета может возникнуть ошибка компиляции, аналогичная описанию "Предопределенный тип ValueTuple(Of,,,)" не определен или импортирован".

Инстанцирование и использование кортежа

Создайте экземпляр кортежа, заключив значения с разделителями-запятыми в скобки. Затем каждое из этих значений становится полем кортежа. Например, следующий код определяет тройку (или 3-кортеж) с Date в качестве первого значения, String в качестве второго, и Boolean в качестве третьего.

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

По умолчанию имя каждого поля в кортеже состоит из строки 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. Затем он отображает значения полей, изменяет их и снова отображает значения полей.

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 может определять имена элементов кортежей, их не нужно назначать явно. Выводимые имена кортежей полезны при инициализации кортежа из набора переменных, когда вы хотите, чтобы имя элемента кортежа было таким же, как имя переменной.

В следующем примере создается stateInfo кортеж, содержащий три элемента с явными именами: state, stateName и capital. Обратите внимание, что при именовании элементов оператор инициализации кортежа просто назначает именованные элементы значения идентичных именованных переменных.

Const state As String = "MI"
Const stateName As String = "Michigan"
Dim stateInfo = (state:=state, stateName:=stateName)
Console.WriteLine($"{stateInfo.stateName}: 2-letter code: {stateInfo.state}")
' 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 для использования в файле проекта Visual Basic (*.vbproj):

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

Номер версии может быть любой версией компилятора Visual Basic, начиная с версии 15.3. Вместо жесткого написания определенной версии компилятора можно также указать "Последняя" в качестве значения LangVersion для компиляции с последней версией компилятора Visual Basic, установленной в вашей системе.

Дополнительные сведения см. в разделе "Настройка версии языка Visual Basic".

В некоторых случаях компилятор Visual Basic не может выводить имя элемента кортежа из имени кандидата, а поле кортежа можно ссылаться только с помощью его имени по умолчанию, например Item1, Item2и т. д. К ним относятся:

  • Имя кандидата совпадает с именем элемента кортежа, например Item3, Restили ToString.

  • Имя кандидата повторяется в кортеже.

Если вывод имени поля завершается ошибкой, Visual Basic не создает ошибку компилятора и не вызывает исключение во время выполнения. Вместо этого поля кортежа должны ссылаться на их предопределенные имена, например Item1 и Item2.

Кортежи и структуры

Кортеж Visual Basic — это тип значения, который является экземпляром одного из универсальных типов System.ValueTuple . Например, кортеж holiday, определенный в предыдущем примере, является экземпляром структуры ValueTuple<T1,T2,T3>. Он предназначен быть легковесным контейнером для данных. Так как кортеж предназначен для упрощения создания объекта, содержащего несколько элементов данных, он не имеет некоторых функций, которые может иметь пользовательская структура. Например:

  • Пользовательские члены. Вы не можете определить собственные свойства, методы или события для кортежа.

  • Проверка. Невозможно проверить данные, назначенные полям.

  • Неизменяемость. Кортежи Visual Basic изменяются. В отличие от этого, пользовательская структура позволяет контролировать, является ли экземпляр изменяемым или неизменяемым.

Если важны пользовательские элементы, проверка свойств и полей, или неизменяемость, следует использовать оператор Structure Visual Basic для определения пользовательского типа значения.

Кортеж Visual Basic наследует элементы своего типа ValueTuple . Помимо полей, к ним относятся следующие методы:

Метод Описание
CompareTo Сравнивает текущий кортеж с другим кортежем с тем же количеством элементов.
Равно Определяет, равен ли текущий кортеж другому кортежу или объекту.
GetHashCode Вычисляет хэш-код для текущего экземпляра .
ToString Возвращает строковое представление этого кортежа, которое принимает форму (Item1, Item2...), где Item1 и Item2 представляет значения полей кортежа.

Кроме того, типы ValueTuple реализуют IStructuralComparable и IStructuralEquatable интерфейсы, которые позволяют задавать пользовательские компараторы.

Назначение и кортежи

Visual Basic поддерживает назначение между типами кортежей с одинаковым количеством полей. Типы полей можно преобразовать, если одно из следующих условий истинно:

  • Исходное и целевое поле имеют одинаковый тип.

  • Определяется расширение (или неявное) преобразование исходного типа в целевой тип.

  • Option Strict и On — это типы, между которыми определено сужающее (или явное) преобразование из исходного типа в целевой. Это преобразование может вызвать исключение, если исходное значение находится за пределами диапазона целевого типа.

Другие преобразования в контексте назначений не учитываются. Рассмотрим возможные виды назначений между типами кортежей.

Следует учитывать следующие переменные в приведенных ниже примерах:

' 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")

Первые две переменные unnamed и anonymousне имеют семантических имен, предоставленных для полей. Их имена полей являются именами по умолчанию Item1 и Item2. Последние две переменные named и differentName имена семантических полей. Обратите внимание на то, что поля в этих двух кортежах называются по-разному.

Все четыре из этих кортежей имеют одинаковое количество полей (так называемую «арность»), а типы этих полей идентичны. Таким образом, все эти задачи работают:

' 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 кортежу conversion, даже если первое поле named является Integer, а первое поле conversion является Long. Это назначение завершается успешно, так как преобразование Integer в объект Long является расширением.

' 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

Кортежи как возвращаемые значения методов

Метод может возвращать только одно значение. Однако часто требуется вызов метода для возврата нескольких значений. Существует несколько способов обойти это ограничение:

  • Можно создать пользовательский класс или структуру, свойства или поля которых представляют значения, возвращаемые методом. Это громоздкое решение; это требует определения пользовательского типа, единственная цель которого — извлечение значений из вызова метода.

  • Можно вернуть одно значение из метода и вернуть оставшиеся значения, передав их по ссылке на метод. Это включает затраты на создание экземпляра переменной и риск непреднамеренного перезаписывания значения переменной, передаваемой по ссылке.

  • Вы можете использовать кортеж, который предоставляет легковесное решение для получения нескольких значений.

Например, методы TryParse в .NET возвращают значение, которое указывает, успешно ли выполнена операция разбора. Результат операции синтаксического анализа возвращается в переменной, передаваемой ссылкой на метод. Обычно вызов метода синтаксического анализа, например Integer.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

Мы можем вернуть кортеж из операции синтаксического анализа, если в нашем собственном методе заключим вызов метода Integer.TryParse. В следующем примере NumericLibrary.ParseInteger вызывает метод Integer.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 — это экземпляр одного из универсальных типов System.ValueTuple, которые были представлены в платформе .NET Framework 4.7. Платформа .NET Framework также включает набор универсальных классов System.Tuple. Однако эти классы отличаются от кортежей Visual Basic и универсальных типов System.ValueTuple несколькими способами:

  • Элементы классов Tuple — это свойства с именем Item1, Item2, и т. д. В кортежах Visual Basic и типах ValueTuple элементы кортежа являются полями.

  • Нельзя назначать значимые имена элементам экземпляра Tuple или экземпляра ValueTuple. Visual Basic позволяет назначать имена, которые передают значение полей.

  • Свойства экземпляра Кортежа являются доступными только для чтения. Кортежи неизменяемы. В кортежах Visual Basic и типах ValueTuple поля кортежей считываются и записываются; кортежи являются изменяемыми.

  • Универсальные типы кортежей являются типами ссылок. Использование этих Tuple-типов означает выделение объектов. На горячих участках кода это может оказывать заметное воздействие на производительность вашего приложения. Типы значений представлены кортежами Visual Basic и типами ValueTuple.

Методы расширения в TupleExtensions классе упрощают преобразование между кортежами Visual Basic и объектами кортежей .NET. Метод ToTuple преобразует кортеж Visual Basic в объект Tuple .NET, а метод ToValueTuple преобразует объект Tuple .NET в кортеж Visual Basic.

В следующем примере создается кортеж, который преобразуется в объект Tuple .NET и затем преобразуется обратно в кортеж 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

См. также