Sobre la implementación interna de las tuplas-valor (3)

En la entrada anterior mostramos cómo el compilador de C# 7.0 y posteriores genera atributos TupleElementNames (espacio de nombres System.Runtime.CompilerServices) para los parámetros de entrada y valores de retorno de métodos que sean tuplas, y cómo estos atributos son luego utilizados por el propio compilador para permitirnos utilizar nombres menos áridos que Item1, Item2, etc. para referirnos a los elementos de cada una de esas tuplas. Esta generación de atributos TupleElementNames no se limita únicamente a los argumentos y valores de retorno de funciones. Por ejemplo, el siguiente programa compila y ejecuta correctamente de C# 7.0 en adelante:

01 using System;
02 class MainClass
03 {
04     static readonly (string Name, int Age) tuple = ("Denis"31);
05
06     static void Main()
07     {
08         (double Re, double Im) i = (01);
09         // Imprime '(0, 1)'
10         Console.WriteLine($"({i.Re}{i.Im})");
11         // Imprime 'Denis is 31'
12         Console.WriteLine($"{tuple.Name} is {tuple.Age}");
13     }
14 }

Si se compila y luego desensambla el código, se verá algo como lo siguiente:

internal class MainClass
{
    // Static Fields
    [TupleElementNames(new string[] { "Name", "Age" })]
    private static readonly ValueTuple<stringint> tuple = 
        new ValueTuple<stringint> ("Denis"31);

    // Static Methods
    private static void Main ()
    {
        ValueTuple<doubledouble> valueTuple = 
            new ValueTuple<doubledouble> (0.01.0);
        Console.WriteLine (string.Format ("({0}, {1})", 
            valueTuple.Item1, valueTuple.Item2));
        Console.WriteLine (string.Format ("{0} is {1}", 
            MainClass.tuple.Item1, MainClass.tuple.Item2));
    }
}

En este caso, el atributo TupleElementNames se aplica a un campo estático, que podría ser público y con ello accesible desde otros ensamblados. Observe, por otra parte, que no se ha generado el atributo para la variable local i; el análisis de flujo del compilador detecta que ésta no podrá ser accedida desde fuera del ámbito en el que ha sido definida, y por lo tanto el atributo sería superfluo.

Personalmente, creo que preferiré siempre que se pueda utilizar un estilo similar al usado en la declaración del campo tuple en el código anterior (línea 04), asignando tipos y nombres explícitos a los elementos de las tuplas del lado izquierdo de la igualdad. Asimismo, utilizaré notación Pascal para esos nombres (como en Name y Age), lo que no solo me gusta más, sino además es lo que recomienda Microsoft para campos públicos en los convenios de nombres de .NET. Ahora bien, ¿qué otras alternativas tenemos a nuestro alcance a la hora de declarar e inicializar un campo o variable de tipo de tupla? Pues podríamos enumerar las siguientes:

a) Tanto para campos como para variables locales, simplemente escribir: (stringint) tupla = (Denis31);

Obviamente, el código cliente tendrá que referirse a los elementos de la dupla utilizando los nombres Item1 e Item2.

b) Para variables locales, utilizar var: var i = (01);

Tal como está escrito el código, también habría que utilizar Item1 e Item2 ; pero ¡siga leyendo!

c) Resulta que la sintaxis para constantes de tuplas permite (por ortogonalidad con los tipos anónimos) establecer nombres para los elementos como parte del literal de tupla; por lo tanto, se obtendría el mismo efecto de la línea 08 del ejemplo al escribir1: var i = (Re: 0.0, Im: 1.0);

Esto sólo funciona para variables locales declaradas mediante var; en otro caso, se produce la advertencia “The tuple element name ‘X‘ is ignored, because a different name or no name is specified for the target type“.

d) Por último, debemos mencionar que C# 7.1 ha añadido un pequeño detalle adicional a lo planteado en el inciso anterior, permitiendo que los nombres de los elementos de las tuplas se infieran a partir de los nombres de las variables utilizadas para inicializar dichos elementos. Por ejemplo, considere el siguiente pequeño programa:

01 using System;
02 class MainClass
03 {
04     static void Main()
05     {
06         double re = 0.0, im = 1.0;
07         var i = (re, im);
08
09         // Imprime '(0, 1)' (en C# 7.1) 
10         Console.WriteLine($"({i.re}{i.im})");
11     }
12 }

Compilado con C# 7.1, el programa anterior ejecuta correctamente, porque los nombres re e im se infieren para los elementos de i. Pero si se compila con C# 7.0, esos elementos no tienen nombre explícito y el compilador se queja en la línea 10 de que i no tiene ningún miembro llamado re, aunque lo hace de una manera muy simpática, como aludiendo al futuro: “Tuple element name ‘re‘ is inferred. Please use version 7.1 or higher to access an element by its inferred name“. Obviamente, este mensaje se modificó cuando C# 7.1 salió a la luz.

La versión del compilador a utilizar se selecciona en las opciones del proyecto:


1 Pero note que deberá incluir el “.0” para las constantes – en caso contrario, sería una dupla con dos elementos enteros.

Octavio Hernandez

Desarrollador y consultor en tecnologías .NET. Microsoft C# MVP entre 2004 y 2010.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *