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

Al final de la entrada anterior mostramos cómo el compilador de C# 7.0 y posteriores sintetiza un atributo TupleElementNames (espacio de nombres System.Runtime.CompilerServices) cada vez que una función devuelve una tupla. Lo mismo ocurre en el caso de que uno o más parámetros de entrada sean tuplas. Por ejemplo, para la siguiente función, que recibe dos parámetros de tipos de tuplas y retorna una tercera:

(string Name, int Age) Transform(
    (string FirstName, string LastName) name, 
    (int Year, int Month, int Day) birthDate)
{
    var fullName = $"{name.FirstName} {name.LastName}";
    var age = Years(
        new DateTime(birthDate.Year, birthDate.Month, birthDate.Day), 
        DateTime.Now);
    return (fullName, age);
}

se genera el siguiente código:

[return: TupleElementNames(new string[] { "Name", "Age" })]
private ValueTuple<stringint> Transform (
    [TupleElementNames(new string[] { "FirstName", "LastName" })]
    ValueTuple<stringstring> name, 
    [TupleElementNames(new string[] { "Year", "Month", "Day" })]
    ValueTuple<intintint> birthDate)
{
    string fullName = string.Format("{0} {1}", name.Item1, name.Item2);
    int age = MainClass.Years(
        new DateTime (birthDate.Item1, birthDate.Item2, birthDate.Item3), 
        DateTime.Now);
    return new ValueTuple<stringint> (fullName, age);
}

Recordando cuál es el objetivo central para el que fueron diseñados los atributos de C#, el de asociar información adicional de metadatos a tipos o miembros de un tipo para que posteriormente pueda ser consumida por herramientas externas1, queda claro por qué razón se asocian estos atributos a las tuplas: para que el propio compilador de C# los lea al procesar tanto un fichero de código fuente como un ensamblado compilado del que no dispongamos del código fuente, y de esta forma se haga posible que se nos permita escribir x.Name o x.Age en lugar de la notación más críptica x.Item1 o x.Item2 (que, en cualquier caso, sigue estando disponible) para acceder a los miembros de la dupla devuelta por Transform.

Observe, sin embargo, que esta implementación mediante atributos de los nombres de elementos nos podría obligar en ciertos casos a escribir código adicional. En el fondo, los verdaderos nombres de los campos son Item1, Item2, etc., y tendremos que personalizar a medida, por ejemplo, el código que hace reflexión o serialización si quisiéramos aprovechar los nombres mnemotécnicos en esos escenarios. Por ejemplo, la serialización directa a JSON de un objeto devuelto por la función anterior utilizando el método JsonConvert.SerializeObject de la librería JSON.NET producirá simplemente:

{"Item1":"Denis","Item2":31}

En nuestra próxima entrega continuaremos hablando un poco más sobre este mismo tema, que ha dado bastante de sí.


1 Generalmente prefiero los términos que se utilizan en C# con respecto a los usados para conceptos similares o equivalentes de Java, pero creo aquí Java se llevó las palmas: allí se habla de anotaciones (annotations) en lugar de atributos.

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 *