Tuplas (Visual Basic)

A partir de Visual Basic 2017, el lenguaje Visual Basic ofrece compatibilidad integrada con tuplas que facilitan la creación de tuplas y el acceso a los elementos de las tuplas. Una tupla es una estructura de datos ligera que tiene un número específico y una secuencia de valores. Al crear una instancia de la tupla, se define el número y el tipo de datos de cada valor (o elemento). Por ejemplo, una tupla de 2 (o par) tiene dos elementos. El primero podría ser un valor Boolean, mientras que el segundo es un String. Dado que las tuplas facilitan el almacenamiento de varios valores en un solo objeto, a menudo se usan como una manera ligera de devolver varios valores de un método.

Importante

La compatibilidad con tuplas requiere el tipo ValueTuple. Si .NET Framework 4.7 no está instalado, debe agregar el paquete NuGet System.ValueTuple, que está disponible en la Galería de NuGet. Sin este paquete, puede obtener un error de compilación similar al "Tipo predefinido "ValueTuple(Of,,,)" no está definido ni importado".

Creación de instancias y uso de una tupla

Para crear una instancia de una tupla, incluya sus valores delimitados por comas entre paréntesis. Cada uno de esos valores se convierte en un campo de la tupla. Por ejemplo, el código siguiente define un triple (o 3-tupla) con un Date como su primer valor, un String como su segundo y un Boolean como tercer valor.

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

De forma predeterminada, el nombre de cada campo de una tupla consta de la cadena Item junto con la posición de base única del campo en la tupla. Para esta tupla 3, el campo es Date, el campo es Item1, el campo String es Item2 y el campo Boolean es Item3. En el ejemplo siguiente se muestran los valores de los campos de la tupla creada en la línea 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

Los campos de una tupla de Visual Basic son de lectura y escritura; después de crear una instancia de una tupla, puede modificar sus valores. En el ejemplo siguiente se modifican dos de los tres campos de la tupla creada en el ejemplo anterior y se muestra el 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

Creación de instancias y uso de una tupla con nombre

En lugar de usar nombres predeterminados para los campos de una tupla, puede crear una instancia de una tupla con nombre asignando sus propios nombres a los elementos de la tupla. Los campos de la tupla pueden acceder a ellos por sus nombres asignados o por sus nombres predeterminados. En el ejemplo siguiente se crea una instancia de la misma tupla de tres que anteriormente, salvo que asigna explícitamente el nombre del primer campo EventDate, el segundo Name y el tercero IsHoliday. A continuación, muestra los valores de campo, los modifica y vuelve a mostrar los valores de campo.

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

También puede especificar los nombres de tupla como parte de la declaración de tipo de una variable, campo o 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

o en el tipo de valor devuelto de un método.

Esto es especialmente útil al proporcionar tuplas a un inicializador de colección; los nombres de tupla se pueden proporcionar como parte de la declaración de tipo de la colección:

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

Nombres de elementos de tupla inferidos

A partir de Visual Basic 15.3, Visual Basic puede deducir los nombres de los elementos de tupla; no es necesario asignarlos explícitamente. Los nombres de tuplas inferidos son útiles al inicializar una tupla a partir de un conjunto de variables y desea que el nombre del elemento de tupla sea el mismo que el nombre de la variable.

En el ejemplo siguiente se crea una tupla stateInfo que contiene tres elementos con nombre explícito, state, stateName y capital. Tenga en cuenta que, al asignar nombres a los elementos, la instrucción de inicialización de tupla simplemente asigna los elementos con nombre a los valores de las variables con nombre idéntico.

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

Dado que los elementos y variables tienen el mismo nombre, el compilador de Visual Basic puede deducir los nombres de los campos, como se muestra en el ejemplo siguiente.

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 los nombres de elementos de tupla inferidos, debe definir la versión del compilador de Visual Basic que se usará en el archivo del proyecto de Visual Basic (*.vbproj):

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

El número de versión puede ser cualquier versión del compilador de Visual Basic a partir de la versión 15.3. En lugar de codificar de forma rígida una versión específica del compilador, también puede especificar "Latest" como valor de LangVersion para compilar con la versión más reciente del compilador de Visual Basic instalada en el sistema.

Para obtener más información, consulte la configuración de la versión de lenguaje de Visual Basic.

En algunos casos, el compilador de Visual Basic no puede deducir el nombre del elemento de tupla del nombre candidato y el campo de tupla solo se puede hacer referencia mediante su nombre predeterminado, como Item1, Item2, etc. Estos incluyen:

  • El nombre del candidato es el mismo que el nombre de un miembro de tupla, como Item3, Rest o ToString.

  • El nombre del candidato se duplica en la tupla.

Cuando se produce un error en la inferencia de nombres de campo, Visual Basic no genera un error del compilador ni se produce una excepción en tiempo de ejecución. En su lugar, sus nombres predefinidos deben hacer referencia a los campos de tupla, como Item1 y Item2.

Tuplas frente a estructuras

Una tupla de Visual Basic es un tipo de valor que es una instancia de uno de los tipos genéricos System.ValueTuple. Por ejemplo, la tupla holiday definida en el ejemplo anterior es una instancia de la estructura ValueTuple<T1,T2,T3>. Está diseñado para ser un contenedor ligero para los datos. Dado que la tupla tiene como objetivo facilitar la creación de un objeto con varios elementos de datos, carece de algunas de las características que podría tener una estructura personalizada. Entre ellas se incluyen las siguientes:

  • Miembros personalizados. No se pueden definir las propias propiedades, métodos o eventos para una tupla.

  • Validation (Validación). No se pueden validar los datos asignados a campos.

  • Inmutabilidad. Las tuplas de Visual Basic son mutables. Por el contrario, una estructura personalizada permite controlar si una instancia es mutable o inmutable.

Si los miembros personalizados, la validación de propiedades y campos o la inmutabilidad son importantes, debe usar la instrucción Estructura de Visual Basic para definir un tipo de valor personalizado.

Una tupla de Visual Basic hereda los miembros de su tipo ValueTuple. Además de sus campos, estos incluyen los métodos siguientes:

Método Descripción
CompareTo Compara la tupla actual con otra tupla con el mismo número de elementos.
Equals Determina si la tupla actual es igual a otra tupla u objeto.
GetHashCode Calcula el código hash de la instancia actual.
ToString Devuelve la representación de cadena de esta tupla, que toma el formato (Item1, Item2...), donde Item1 y Item2 representan los valores de los campos de la tupla.

Además, los tipos ValueTuple implementan las interfaces IStructuralComparable e IStructuralEquatable, lo que le permite definir comparadores personalizados.

Asignación y tuplas

Visual Basic admite la asignación entre tipos de tupla que tienen el mismo número de campos. Los tipos de campo se pueden convertir si se cumple una de las siguientes condiciones:

  • El campo de origen y destino son del mismo tipo.

  • Se define una conversión de ampliación (o implícita) del tipo de origen al tipo de destino.

  • Option Strict es Ony se define una conversión de restricción (o explícita) del tipo de origen al tipo de destino. Esta conversión puede producir una excepción si el valor de origen está fuera del intervalo del tipo de destino.

Otras conversiones no se tienen en cuenta para las asignaciones. Echemos un vistazo a los tipos de asignaciones que se permiten entre los tipos de tupla.

Tenga en cuenta estas variables que se usan en los ejemplos siguientes:

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

Las dos primeras variables, unnamed y anonymous, no tienen nombres semánticos proporcionados para los campos. Sus nombres de campo son los valores predeterminados Item1 y Item2. Las dos últimas variables, named y differentName, tienen nombres semánticos asignados a los campos. Tenga en cuenta que estas dos tuplas tienen nombres diferentes para los campos.

Estas cuatro tuplas tiene el mismo número de campos (denominados "aridad") y los tipos de esos campos son idénticos. Por consiguiente, todas estas asignaciones funcionan:

' 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 los nombres de las tuplas no se asignan. Los valores de los campos se asignan según el orden de los campos de la tupla.

Por último, observe que podemos asignar la tupla named a la tupla conversion, aunque el primer campo de named es , Integer y el primer campo de conversion es Long. Esta asignación se realiza correctamente porque la conversión de un objeto Integer a Long es una conversión de ampliación.

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

Las tuplas de diferentes tipos o números de campos no son asignables:

' 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 devueltos del método

Un método solo puede devolver un valor único. Sin embargo, con frecuencia, le gustaría una llamada de método para devolver varios valores. Hay varias maneras de solucionar esta limitación.

  • Puede crear una clase o estructura personalizada cuyas propiedades o campos representan los valores devueltos por el método . Se trata de una solución de peso pesado; requiere que defina un tipo personalizado cuyo único propósito es recuperar valores de una llamada de método.

  • Puede devolver un valor único del método y devolver los valores restantes pasándolos por referencia al método. Esto implica la sobrecarga de crear instancias de una variable y se corre el riesgo de sobrescribir accidentalmente el valor de la variable que se pasa por referencia.

  • Puede usar una tupla, que proporciona una solución ligera para recuperar varios valores devueltos.

Por ejemplo, los métodos TryParse de .NET devuelven un valor Boolean que indica si la operación de análisis se realizó correctamente. El resultado de la operación de análisis se devuelve en una variable pasada por referencia al método. Normalmente, una llamada a un método de análisis, como, por ejemplo, Int32.TryParse, tiene el siguiente aspecto:

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 devolver una tupla de la operación de análisis si encapsulamos la llamada al método Int32.TryParse en nuestro propio método. En el ejemplo siguiente, NumericLibrary.ParseInteger llama al método Int32.TryParse y devuelve una tupla con nombre con dos 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

Después, puede llamar al método con código como el siguiente:

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 y tuplas de Visual Basic en .NET Framework

Una tupla de Visual Basic es una instancia de uno de los tipos genéricos System.ValueTuple, que se introdujeron en .NET Framework 4.7. .NET Framework también incluye un conjunto de clases genéricas System.Tuple. Sin embargo, estas clases difieren de las tuplas de Visual Basic y de los tipos genéricos System.ValueTuple de varias maneras:

  • Los elementos de las clases Tuple son propiedades denominadas Item1, Item2, etc. En las tuplas de Visual Basic y en los tipos ValueTuple, los elementos de tupla son campos.

  • No se pueden asignar nombres significativos a los elementos de una instancia de Tuple o de una instancia de ValueTuple. Visual Basic permite asignar nombres que comunican el significado de los campos.

  • Las propiedades de una instancia de Tuple son de solo lectura; las tuplas son inmutables. En las tuplas de Visual Basic y en los tipos ValueTuple, los campos de tupla son de lectura y escritura; las tuplas son mutables.

  • Los tipos de tupla genéricos son tipos de referencia. El uso de estos tipos de tupla significa asignar objetos. En rutas de acceso activas, esto puede suponer un importante impacto en el rendimiento de la aplicación. Las tuplas de Visual Basic y los tipos ValueTuple son tipos de valor.

Los métodos de extensión de la clase TupleExtensions facilitan la conversión entre las tuplas de Visual Basic y los objetos Tuple de .NET. El método ToTuple convierte una tupla de Visual Basic en un objeto Tuple de .NET y el método ToValueTuple convierte un objeto Tuple de .NET en una tupla de Visual Basic.

En el ejemplo siguiente se crea una tupla, se convierte en un objeto Tuple de .NET y se convierte de nuevo en una tupla de Visual Basic. A continuación, el ejemplo compara esta tupla con la original para asegurarse de que son iguales.

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

Vea también