Tuplas (Visual Basic)
A partir do Visual Basic 2017, a linguagem Visual Basic oferece suporte interno para tuplas que facilita a criação de tuplas e o acesso aos elementos de tuplas. Uma tupla é uma estrutura de dados leve que tem um número específico e uma sequência de valores. Ao instanciar a tupla, você define o número e o tipo de dados de cada valor (ou elemento). Por exemplo, uma 2-tupla (ou par) tem dois elementos. O primeiro pode ser um Boolean
valor, enquanto o segundo é um String
. Como as tuplas facilitam o armazenamento de vários valores em um único objeto, elas geralmente são usadas como uma maneira leve de retornar vários valores de um método.
Importante
O suporte de tupla requer o ValueTuple tipo. Se o .NET Framework 4.7 não estiver instalado, você deverá adicionar o pacote System.ValueTuple
NuGet , que está disponível na Galeria NuGet. Sem este pacote, você pode obter um erro de compilação semelhante a "O tipo predefinido 'ValueTuple(Of,,,)' não é definido ou importado."
Instanciando e usando uma tupla
Você instancia uma tupla colocando entre parênteses seus valores delimitados por vírgula. Cada um desses valores torna-se então um campo da tupla. Por exemplo, o código a seguir define uma tripla (ou 3-tupla) com a como Date
seu primeiro valor, a String
como seu segundo e a Boolean
como seu terceiro.
Dim holiday = (#07/04/2017#, "Independence Day", True)
Por padrão, o nome de cada campo em uma tupla consiste na cadeia de caracteres Item
junto com a posição única do campo na tupla. Para esta 3-tupla, o Date
campo é Item1
, o String
campo é Item2
, e o Boolean
campo é Item3
. O exemplo a seguir exibe os valores dos campos da tupla instanciada na linha de código anterior
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
Os campos de uma tupla do Visual Basic são leitura-gravação; Depois de instanciar uma tupla, você pode modificar seus valores. O exemplo a seguir modifica dois dos três campos da tupla criada no exemplo anterior e exibe o resultado.
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
Instanciando e usando uma tupla nomeada
Em vez de usar nomes padrão para os campos de uma tupla, você pode instanciar uma tupla nomeada atribuindo seus próprios nomes aos elementos da tupla. Os campos da tupla podem então ser acessados por seus nomes atribuídos ou por seus nomes padrão. O exemplo a seguir instancia a mesma 3-tupla que anteriormente, exceto que ele nomeia explicitamente o primeiro campo EventDate
, o segundo Name
e o terceiro IsHoliday
. Em seguida, exibe os valores de campo, modifica-os e exibe os valores de campo novamente.
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
Você também pode especificar os nomes de tupla como parte da declaração de tipo de uma variável, campo ou parâmetro:
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
ou no tipo de retorno de um método.
Isso é particularmente útil ao fornecer tuplas para um inicializador de coleção; Os nomes das tuplas podem ser fornecidos como parte da declaração de tipo da coleção:
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
Nomes de elementos de tupla inferidos
A partir do Visual Basic 15.3, o Visual Basic pode inferir os nomes dos elementos de tupla; não é necessário atribuí-los explicitamente. Os nomes de tupla inferidos são úteis quando você inicializa uma tupla a partir de um conjunto de variáveis e deseja que o nome do elemento de tupla seja o mesmo que o nome da variável.
O exemplo a seguir cria uma tupla stateInfo
que contém três elementos explicitamente nomeados, state
, stateName
, e capital
. Observe que, ao nomear os elementos, a instrução de inicialização de tupla simplesmente atribui aos elementos nomeados os valores das variáveis nomeadas de forma idêntica.
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
Como elementos e variáveis têm o mesmo nome, o compilador do Visual Basic pode inferir os nomes dos campos, como mostra o exemplo a seguir.
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
Para habilitar nomes de elementos de tupla inferidos, você deve definir a versão do compilador do Visual Basic para usar em seu arquivo de projeto do Visual Basic (*.vbproj):
<PropertyGroup>
<LangVersion>15.3</LangVersion>
</PropertyGroup>
O número da versão pode ser qualquer versão do compilador do Visual Basic começando com 15.3. Em vez de codificar uma versão específica do compilador, você também pode especificar "Mais recente" como o valor de LangVersion
compilar com a versão mais recente do compilador do Visual Basic instalado em seu sistema.
Para obter mais informações, consulte a configuração da versão da linguagem Visual Basic.
Em alguns casos, o compilador do Visual Basic não pode inferir o nome do elemento de tupla do nome candidato, e o campo de tupla só pode ser referenciado usando seu nome padrão, como Item1
, Item2
, etc. Estes incluem:
O nome do candidato é o mesmo que o nome de um membro da tupla, como
Item3
,Rest
, ouToString
.O nome do candidato é duplicado na tupla.
Quando a inferência de nome de campo falha, o Visual Basic não gera um erro de compilador, nem é lançada uma exceção em tempo de execução. Em vez disso, os campos de tupla devem ser referenciados por seus nomes predefinidos, como Item1
e Item2
.
Tuplas versus estruturas
Uma tupla do Visual Basic é um tipo de valor que é uma instância de um dos tipos genéricos System.ValueTuple . Por exemplo, a tupla holiday
definida no exemplo anterior é uma instância da ValueTuple<T1,T2,T3> estrutura. Ele foi projetado para ser um contêiner leve para dados. Como a tupla visa facilitar a criação de um objeto com vários itens de dados, ela não possui alguns dos recursos que uma estrutura personalizada pode ter. Estes são, entre outros:
Membros personalizados. Você não pode definir suas próprias propriedades, métodos ou eventos para uma tupla.
Validação. Não é possível validar os dados atribuídos aos campos.
Imutabilidade. As tuplas do Visual Basic são mutáveis. Por outro lado, uma estrutura personalizada permite controlar se uma instância é mutável ou imutável.
Se membros personalizados, validação de propriedade e campo ou imutabilidade forem importantes, você deve usar a instrução Visual Basic Structure para definir um tipo de valor personalizado.
Uma tupla do Visual Basic herda os membros de seu tipo ValueTuple . Além de seus campos, estes incluem os seguintes métodos:
Método | Description |
---|---|
CompareTo | Compara a tupla atual com outra tupla com o mesmo número de elementos. |
Igual a | Determina se a tupla atual é igual a outra tupla ou objeto. |
GetHashCode | Calcula o código hash para a instância atual. |
ToString | Devolve a representação de cadeia de caracteres desta tupla, que assume a forma (Item1, Item2...) , onde Item1 e Item2 representam os valores dos campos da tupla. |
Além disso, os tipos ValueTuple implementam IStructuralComparable e IStructuralEquatable interfaces, que permitem definir comparadores personalizados.
Atribuição e tuplas
Visual Basic suporta a atribuição entre tipos de tupla que têm o mesmo número de campos. Os tipos de campo podem ser convertidos se uma das seguintes opções for verdadeira:
O campo de origem e o campo de destino são do mesmo tipo.
É definida uma conversão alargada (ou implícita) do tipo de origem para o tipo de destino.
Option Strict
éOn
, e uma conversão restrita (ou explícita) do tipo de origem para o tipo de destino é definida. Essa conversão pode gerar uma exceção se o valor de origem estiver fora do intervalo do tipo de destino.
Outras conversões não são consideradas para atribuições. Vejamos os tipos de atribuições que são permitidos entre tipos de tupla.
Considere essas variáveis usadas nos seguintes exemplos:
' 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")
As duas primeiras variáveis unnamed
e anonymous
, não têm nomes semânticos fornecidos para os campos. Seus nomes de campo são o padrão Item1
e Item2
. As duas últimas variáveis named
e differentName
têm nomes de campos semânticos. Observe que essas duas tuplas têm nomes diferentes para os campos.
Todas estas quatro tuplas têm o mesmo número de campos (referido como «aridade»), e os tipos desses campos são idênticos. Portanto, todas essas atribuições funcionam:
' 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
Observe que os nomes das tuplas não são atribuídos. Os valores dos campos são atribuídos seguindo a ordem dos campos na tupla.
Finalmente, observe que podemos atribuir a tupla named
conversion
à tupla, mesmo que o primeiro campo de named
é um Integer
, e o primeiro campo de conversion
é um Long
. Essa atribuição é bem-sucedida porque a conversão de um Integer
em um é uma conversão em expansão 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)
Tuplas com diferentes números de campos não são atribuíveis:
' 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
Tuplas como valores de retorno de método
Um método pode retornar apenas um único valor. Frequentemente, porém, você gostaria de uma chamada de método para retornar vários valores. Há várias maneiras de contornar essa limitação:
Você pode criar uma classe ou estrutura personalizada cujas propriedades ou campos representam valores retornados pelo método. Esta é uma solução pesada; Ele requer que você defina um tipo personalizado cuja única finalidade é recuperar valores de uma chamada de método.
Você pode retornar um único valor do método e retornar os valores restantes passando-os por referência ao método. Isso envolve a sobrecarga de instanciar uma variável e corre o risco de substituir inadvertidamente o valor da variável que você passa por referência.
Você pode usar uma tupla, que fornece uma solução leve para recuperar vários valores de retorno.
Por exemplo, os métodos TryParse no .NET retornam um Boolean
valor que indica se a operação de análise foi bem-sucedida. O resultado da operação de análise é retornado em uma variável passada por referência ao método. Normalmente, uma chamada para um método de análise como Integer.TryParse tem a seguinte aparência:
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
Podemos retornar uma tupla da operação de análise se encapsularmos a chamada para o método Integer.TryParse em nosso próprio método. No exemplo a seguir, NumericLibrary.ParseInteger
chama o método Integer.TryParse e retorna uma tupla nomeada com dois elementos.
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
Em seguida, você pode chamar o método com código como o seguinte:
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
Tuplas e tuplas do Visual Basic no .NET Framework
Uma tupla do Visual Basic é uma instância de um dos tipos genéricos System.ValueTuple , que foram introduzidos no .NET Framework 4.7. O .NET Framework também inclui um conjunto de classes System.Tuple genéricas. Essas classes, no entanto, diferem das tuplas do Visual Basic e dos tipos genéricos System.ValueTuple de várias maneiras:
Os elementos das classes Tuple são propriedades nomeadas
Item1
,Item2
, e assim por diante. Nas tuplas do Visual Basic e nos tipos ValueTuple, os elementos de tupla são campos.Não é possível atribuir nomes significativos aos elementos de uma instância Tuple ou de uma instância ValueTuple . Visual Basic permite que você atribua nomes que comunicam o significado dos campos.
As propriedades de uma instância de Tuple são somente leitura, as tuplas são imutáveis. Nas tuplas do Visual Basic e nos tipos ValueTuple , os campos de tupla são leitura-gravação, as tuplas são mutáveis.
Os tipos genéricos Tuple são tipos de referência. Usar esses tipos de Tuple significa alocar objetos. Em caminhos ativos, isso pode ter um impacto mensurável no desempenho do seu aplicativo. As tuplas do Visual Basic e os tipos ValueTuple são tipos de valor.
Os métodos de extensão na classe facilitam a TupleExtensions conversão entre tuplas do Visual Basic e objetos Tuple do .NET. O método ToTuple converte uma tupla do Visual Basic em um objeto .NET Tuple e o método ToValueTuple converte um objeto .NET Tuple em uma tupla do Visual Basic.
O exemplo a seguir cria uma tupla, a converte em um objeto .NET Tuple e a converte novamente em uma tupla do Visual Basic. O exemplo então compara essa tupla com a original para garantir que elas sejam iguais.
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