Variables locales y valores de retorno por referencia

“… Turn around/Turn around
It’s on the other side
Feel the sound/Feel the sound
It’s coming from deep inside…”
Kansas, “On the Other Side” (1979)

La entrada anterior tenía al principio como objetivo mostrar el uso de las variables locales y valores de retorno por referencia (ref locals and returns), pero a lo largo del camino me fui desviando de ese objetivo inicial, para terminar hablando de otra cosa totalmente diferente. Con esta entrada intento retomar la senda perdida aquella vez y mostrar la utilización de estas novedosas posibilidades que ofrece C#, aparecidas por primera vez en la versión 7.0.

Estas características tienen como objetivo principal el de introducir una semántica de punteros sin que sea necesario recurrir al código no seguro (unsafe code), con la finalidad última de promover ventajas de rendimiento, de manera similar a los parámetros ref de toda la vida. Por ejemplo, suponiendo que dispusiéramos de una estructura de gran tamaño y necesitáramos pasarla a un método como parámetro, la posibilidad de pasar una referencia a la estructura en lugar de la estructura en sí nos permitiría ahorrar tanto en espacio de pila como en el tiempo necesario para la copia. Aquí me he montado un ejemplo rápido, aunque probablemente haría falta una estructura bastante más grande para que la ganancia fuera perceptible:

namespace RefReturnsAndLocals
{
    public struct Point
    {
        public int X, Y, Z;
        public override string ToString() => $"({X}, {Y}, {Z})";
    }

    static class MainClass
    {
        static void Main()
        {
            Point p = new Point { X = 0, Y = 0, Z = 0 };
            Drift(ref p, 100);
            Console.WriteLine(p);
        }

        static void Drift(ref Point point, int steps)
        {
            var rnd = new Random();
            for (int i = 0; i < steps; i++)
            {
                point.X += rnd.Next(-5, 6);
                point.Y += rnd.Next(-5, 6);
                point.Z += rnd.Next(-5, 6);
            }
        }
    }
}

Las referencias locales operan de manera bastante similar a los parámetros ref; por ejemplo, en el método Drift podríamos haber introducido una variable-referencia local, inicializarla para apuntar al mismo sitio al que apunta el parámetro de entrada y utilizarla en los cálculos:

        static void Drift(ref Point point, int steps)
        {
            ref Point p = ref point;
            var rnd = new Random();
            for (int i = 0; i < steps; i++)
            {
                p.X += rnd.Next(-5, 6);
                p.Y += rnd.Next(-5, 6);
                p.Z += rnd.Next(-5, 6);
            }
        }
    }
}

Observe cómo hay que utilizar ref tanto en la declaración de la variable como en la expresión de inicialización. Al declarar una referencia local como ésta, siempre se la debe inicializar; a partir de C# 7.3 (¿se había Ud. dado cuenta de que ya está disponible?), es posible poner una referencia local a “apuntar” a otro objeto del tipo adecuado en cualquier momento posterior a la inicialización.

Dejaré la presentación de los valores de retorno por referencia, que tienen sus propios detalles interesantes, para la próxima entrega. Pero antes de finalizar aquí, vale la pena recordar una vez más que siempre que se trabaja con punteros, los peligros acechan en todo momento. Por ejemplo, quite el prefijo ref en la declaración de esta última versión del método Drift y en la correspondiente llamada; obtendrá un programa que compila, pero no funciona (¿por qué, amigo lector?).


Referencia musical: “On the Other Side” es el primer tema del “Monolith” (1979), el primer disco que mi banda favorita, Kansas, produjo de manera independiente, un derecho que se ganó a pulso después del enorme éxito de sus proyectos anteriores, “Leftoverture” (1976), “Point of Know Return” (1977) y “Two for the Show” (directo, 1978). Si bien ese disco no alcanzó el nivel de los anteriores, todavía tuvo un relativo éxito; algunos de sus temas aún se dejan oír después de casi 40 años, especialmente “On the Other Side” y “People of the South Wind“, dedicada a la tribu aborigen de la que se derivó originalmente el nombre Kansas.

Contando palabras reservadas

Intentando continuar la serie dedicada a las novedades aparecidas en C# 7.0 y versiones posteriores, se me ocurrió escribir un programa que contara las apariciones de las diferentes palabras reservadas (keywords) y palabras reservadas contextuales (contextual keywords) de C# en un fichero de código fuente individual o un conjunto de ficheros de código fuente alojados en una estructura de carpetas anidadas. Como lejana fuente de inspiración me sirvió el recuerdo de un programa para contar las palabras de un fichero incluido en un libro que ha dejado huella, “The C Programming Language“, de Kernighan y Ritchie.

Originalmente pensé que podría utilizar el programa para mostrar las ventajas que puede aportar al rendimiento la utilización de las variables locales y valores de retorno por referencia (ref locals and returns) incorporadas a C# 7.0. Lo cierto es que a lo largo del camino perdí el norte (tal vez me reencuentre con él en una próxima entrega), pero pienso que el viaje ha valido la pena; en particular, por fin he hecho uso (aunque de una manera trivial, lo reconozco) de las posibilidades que ofrece .NET Compiler Platform, la tecnología anteriormente conocida como Roslyn. Espero que el lector también saque algo positivo de la lectura de esta entrada, y para contribuir más a ello pongo a su disposición el código del proyecto, en el que también se utilizan otras novedades de C# 7.0 como las tuplas-valor o las funciones anidadas.

No me extenderé mucho aquí en una introducción al uso de .NET Compiler Platform, porque el lector encontrará múltiples tutoriales de calidad en la web, en particular para el análisis sintáctico; un buen ejemplo es éste. Baste decir que solo es necesario añadir a su proyecto el paquete de NuGet Microsoft.CodeAnalysis.CSharp, y ello le dará acceso a toda la gama de herramientas para la compilación que ofrece Roslyn. Gracias a esa potencia, el método central de nuestro ejemplo, que procesa un fichero de código fuente C#, consiste en unas pocas líneas:

private static void ProcessFile(string fileName)
{
    var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(fileName));
    Traverse(tree.GetRoot());

    void Traverse(SyntaxNode node)
    {
        foreach (var childToken in node.ChildTokens())
        {
            if (childToken.IsKeyword() || childToken.IsContextualKeyword())
            {
                var text = childToken.Text;
                dict[text] = (dict[text].IsContextual, dict[text].Count + 1);
            }
        }

        foreach (var childNode in node.ChildNodes())
            Traverse(childNode);
    }
}

Las listas completas de palabras reservadas y palabras reservadas contextuales de C# las tomé directamente del código fuente de Roslyn en Github; las que intenté tomar de otras fuentes estaban siempre incompletas, como pude comprobar al ejecutar el programa sobre un conjunto de ficheros de la vida real. Las cinco palabras reservadas más utilizadas en ese conjunto son, en orden descendente: using, public, new, return y private.

El futuro atributo CallerArgumentExpression: un deseo que se cumplirá

“… It’s too bad that all these things
Can only happen in my dreams
Only in dreams, in beautiful dreams …”
Roy Orbison, “In Dreams” (1963)

“… We all know / That people are the same wherever you go …”
Paul McCartney & Stevie Wonder, “Ebony and Ivory” (1982)

Hace unos cuantos meses, mi buen amigo Eduard Tomàs publicó en este mismo sitio una entrada con título auto-descriptivo llamada “Hoy he echado en falta poder definir macros en C#“. En la discusión subsiguiente, especulamos sobre un hipotético atributo de .NET que hiciera posible satisfacer su deseo incumplido. Pues bien, ayer revisando las propuestas de características a añadir en las próximas versiones del lenguaje (en el sitio de Github en el que el equipo de C# publica las minutas de sus reuniones, entre otros documentos muy interesantes) me encontré con la descripción del futuro atributo CallerArgumentExpression, que (en el supuesto de que sea implementado, claro), permitirá lograr fácilmente el efecto que Eduard quería obtener aquella vez.

La propuesta de esta nueva característica está siendo considerada por el equipo de C# desde mayo de 2017, unos cuantos meses antes de que nosotros conversáramos sobre el tema; ello me reafirma una vez más en que, como dijeran magistralmente Paul y Stevie, la gente (y en particular, los programadores) tiene las mismas necesidades en todos los lugares. 🙂

using System.Runtime.CompilerServices;

public static class Check
{
    public static void NotNull<TParameter>(TParameter param, 
        [CallerArgumentExpression("param")] string expr = "")
        where TParameter : class
    {
        if (param == null)
        {
            throw new ArgumentNullException(paramName: expr);
        }
    }
}

Referencia musical: Intenté infructuosamente encontrar alguna canción de mi época que dijera que algunos sueños a veces se cumplen; no encontré ninguna, así que he puesto otra que dice más o menos lo contrario. Su autor fue el genial Roy Orbison, a quien mayormente se le recuerda por haber escrito la canción que sirvió de tema a la película “Pretty Woman“. De Paul McCartney y Stevie Wonder no diré nada más aquí – son dos de mis músicos favoritos y seguramente los he mencionado ya en alguna entrada anterior.

La sentencia switch en C# 7.0 (y 2)

“It never rains in California,
But girl, don’t they warn ya,
It pours, man, it pours…”
Albert Hammond, It Never Rains in Southern California (1972)

Parte 1

En nuestra entrega anterior hablamos sobre las nuevas posibilidades de la sentencia switch, y en particular las relacionadas con la utilización de los tres tipos de patrones soportados por C# 7.0 en las cláusulas case, que mencionamos por primera vez aquí. En dicha última entrega nos quedó pendiente hablar sobre la utilización en el marco de las sentencias switch del tercer tipo de patrones disponible: los patrones var.

De manera similar a lo que ocurre cuando se les usa en una sentencia if, estos patrones casan incondicionalmente, y simplemente asignan el valor de la expresión de switch a la variable asociada, que será del mismo tipo de la expresión:

    switch (obj)
    {
        case var x:
            Console.WriteLine($”Type is {x.GetType().FullName});
            break;
    }

Como mismo dijimos entonces, no parece fácil encontrar una situación práctica donde la utilidad del uso de esta construcción sea evidente. Al menos, no he visto ninguno todavía en ninguno de los artículos que he leído al respecto (incluyendo los de MSDN donde se describe la característica). Pero ya sabemos que la vida es más rica que cualquier referente en el que la queramos enmarcar.

Algunas de las fuentes que he consultado sugieren que el uso de este patrón gana en posibilidades si se le combina con la otra novedad incorporada a la sentencia switch en C# 7.0: la cláusula when, que hace posible adosar una condición a una cláusula case, como se hace desde hace mucho tiempo en Visual Basic:

    switch (p.Age)
    {
        case var n when n < 16:
            Console.WriteLine(“Can’t vote yet!”);
            break;
        default:
            Console.WriteLine(“Potential voter!”);
            break;
    }

La cláusula when se puede asociar también a los patrones de tipo. El código que sigue es una modificación del utilizado en el ejemplo de la entrega anterior, en el que ahora se contabilizan las mascotas según su tipo y las personas según su edad. Reconozco que el ejemplo no es el mejor; observe la cantidad de código que se duplica debido al hecho de que únicamente una de las ramas de una sentencia switch se ejecuta.

class MainClass
{
    static void Main(string[] args)
    {
        Process(
            new Employee("Octavio"55"Software Eng."),
            new Person("Diana"21),
            new Pet("Shelly"PetType.DOG11)
        );
    }

    static void Process(params object[] data)
    {
        int people = 0, oldPeople = 0;
        int pets =  0, dogs = 0, cats = 0;
        foreach (var obj in data)
        {
            switch (obj)
            {
                case null:
                    break;

                case Pet pet when pet.Type == PetType.DOG:
                    Console.WriteLine($"{pet.Name} [{pet.Type}]");
                    pets++;
                    dogs++;
                    break;
                case Pet pet when pet.Type == PetType.CAT:
                    Console.WriteLine($"{pet.Name} [{pet.Type}]");
                    pets++;
                    cats++;
                    break;
                case Pet pet:
                    pets++;
                    break;

                case Employee e:
                    Console.WriteLine($"{e.Name} ({e.Position})");
                    people++;
                    if (e.Age > 50)
                        oldPeople++;
                    break;
                case Person p when p.Age > 50:
                    Console.WriteLine(p.Name);
                    people++;
                    oldPeople++;
                    break;
                case Person p:
                    Console.WriteLine(p.Name);
                    people++;
                    break;

                default:
                    Console.WriteLine("** Invalid input!");
                    break;
            }
        }
        Console.WriteLine($"Total {people} people, {oldPeople} of them old.");
        Console.WriteLine($"Total {pets} pets, of them {dogs} dogs and {cats} cats.");
    }
}

La adición de la cláusula when a la sentencia switch es en buena medida ortogonal con la presencia de esta cláusula en los bloques catch de manejo de excepciones (desde C# 6.0) para implementar lo que se conoce como filtrado de excepciones. Para esto sí que he encontrado algunos casos de uso práctico en la vida real; por ejemplo, cuando se necesita discriminar ciertos tipos de errores durante el acceso a una base de datos de SQL Server según su código (vea, por ejemplo, este enlace).


Referencia musical: Llueve mucho en estos días (suceso bastante muy poco común) aquí en el sur de California, así que echo mano de este viejo clásico de Albert Hammond. Que en realidad no trata tanto del clima local ni la lluvia como de lo mal que lo puedes pasar por estos lares si te toca una racha de mala suerte.

La sentencia switch en C# 7.0

“Si te quiero es porque sos
Mi amor, mi cómplice y todo,
Y en la calle, codo a codo,
Somos mucho más que dos…”
(Poema de Mario Benedetti)

En una entrada anterior reciente presentamos una de las novedades importantes que introdujo C# 7.0, los llamados patrones (patterns), que ahora pueden utilizarse en el contexto de dos construcciones del lenguaje:

  • El operador is de comprobación de tipos, que ahora acepta también en su parte derecha un patrón, además de un tipo.
  • Las cláusulas case de la sentencia switch, que ahora pueden especificar un patrón de emparejamiento, y no únicamente una constante.

En aquella ocasión mostramos cómo hacer uso de los tres tipos de patrones que incorpora C# 7.0 conjuntamente con el operador is; hoy veremos cómo utilizarlos en el marco de una sentencia switch, algo que en la práctica será mucho más común. Para ello, utilizaremos un sencillo ejemplo que contabiliza e imprime los invitados a una fiesta de empresa a la que se admiten también mascotas:

class MainClass
{
    static void Main(string[] args)
    {
        Process(
            new Employee("Octavio"55"Software Eng."),
            new Person("Diana"21),
            new Pet("Shelly"PetType.DOG11)
        );
    }

    static void Process(params object[] data)
    {
        int people = 0pets = 0;
        foreach (var obj in data)
        {
            switch (obj)
            {
                case null:
                    break;
                case Pet pet:
                    Console.WriteLine($"{pet.Name} [{pet.Type}]");
                    pets++;
                    break;
                case Employee e:
                    Console.WriteLine($"{e.Name} ({e.Position})");
                    people++;
                    break;
                case Person p:
                    Console.WriteLine(p.Name);
                    people++;
                    break;
                default:
                    Console.WriteLine("** Invalid input!");
                    break;
            }
        }
        Console.WriteLine($"Total: {people} people, {pets} pets.");
    }
}

Nótese en primer lugar que la expresión de control de la sentencia switch ya no está limitada a los tipos básicos y las cadenas: la expresión puede ser ahora de cualquier tipo, valor o referencia. La primera cláusula case ejemplifica el primer tipo de patrones que estudiamos la vez anterior, los patrones constantes, que es de hecho lo que estábamos acostumbrados a utilizar en cláusulas case en versiones anteriores de C#; solo que ahora null es una constante tan válida como otra cualquiera.

Las tres cláusulas case a continuación ejemplifican el segundo tipo de patrones, los patrones de tipo, que tienen la forma T x, donde T es un tipo y x un identificador de variable; mediante ellos se comprueba si el valor de la expresión es de tipo T, y en caso afirmativo, se asigna dicho valor a la variable x de tipo T. Téngase en cuenta aquí que el valor null nunca “casa” en estos patrones, por lo que es generalmente necesario codificar explícitamente ese caso, tal y como hemos hecho en el ejemplo.

Dejaremos el uso del tercer tipo de patrones (los patrones var) en la sentencia switch para una próxima entrega, para verla en combinación con otra interesante novedad añadida a esta sentencia en C# 7.0: la cláusula when.

Hay que destacar que con la llegada de C# 7.0 y estas nuevas posibilidades, la sentencia switch se convierte en una bestia MUY DIFERENTE de lo que era hasta la versión anterior de C#:

a) En primer lugar, la sentencia se hace mucho más dinámica de lo que fue nunca antes. Ahora los patrones de las cláusulas case se emparejan en tiempo de ejecución. Anteriormente, las cláusulas case solo especificaban constantes (diferentes, añadiría yo), y todo estaba claro ya en tiempo de compilación.

b) En segundo lugar, y estrechamente relacionado con lo anterior, ahora el ORDEN de las cláusulas case en el código fuente es importante; antes era irrelevante.

La semántica de switch ahora consiste, en el caso general, en evaluar secuencialmente (en cascada) cada una de las cláusulas case y ejecutar el bloque de código asociado a la primera cláusula que se satisfaga; cediendo el control al bloque default solamente en caso de que ninguna cláusula case tenga éxito. Dado que en una sentencia switch de versiones anteriores de C# solo había constantes mutuamente excluyentes en las cláusulas case, el compilador tenía múltiples opciones a la hora de traducirla a código de máquina, y la única implementación posible no era una cascada de condicionales, sino varios otros métodos tales como tabla de saltos, búsqueda binaria y otros. Por supuesto, el compilador podría continuar utilizando todas esas técnicas cuando encuentre sentencias switch que no utilicen ninguna de las novedades de C# 7.0.

Por último, y ya para concluir por hoy, señalaremos que en C# 7.0 el compilador sigue haciendo, no obstante lo dicho anteriormente, todo lo que puede por nosotros. Por ejemplo, asumiendo como es de suponer que la clase Employee hereda de Person, nuestro programa de ejemplo no compilará si se intercambian de orden los casos correspondientes en la sentencia switch. Esto es similar a lo que hace el compilador toda la vida en relación con los tipos de excepciones que se especifican en cláusulas catch de un mismo try.


Referencia cultural: Como esta entrada se escribe durante el Día de San Valentín, aprovecho para insertar un fragmento de uno de los muchos lindos poemas de amor de ese gigante de la poesía iberoamericana que es el uruguayo Mario Benedetti.

Libro “Programming the Microsoft Bot Framework”

Por fin he logrado arañar el tiempo necesario para leer con detalle el libro “Programming the Microsoft Bot Framework. A Multiplatform Approach to Building Chatbots” (Microsoft Press/Pearson Education, 2018), escrito por mi buen amigo Joe Mayo, a quien sigo desde el ya lejano 2009, cuando publicó su excelente “LINQ Programming“. Recomiendo de corazón el nuevo libro a todo aquel que quiera adentrarse en esta novedosa tecnología de Microsoft, empezando desde los fundamentos básicos y llegando hasta temas avanzados como la creación de canales de control personalizados o la integración con servicios de voz, correo, SMS y web, así como con los Servicios Cognitivos de Microsoft (Microsoft Cognitive Services), que potencian las modernas aplicaciones basadas en Inteligencia Artificial. Puede encontrar la Tabla de Contenidos del libro y algunas páginas de ejemplo en la página del libro en Amazon.com.

Debo mencionar que el autor se puso de muy mala suerte y dos de las API en las que se apoyó para el desarrollo de los ejemplos incluidos en el libro, Wine.com y Groove, fueron abandonadas por sus respectivos creadores después del envío del libro a imprenta. Pero esto es algo que sucede con cierta frecuencia cuando se experimenta con tecnologías novedosas, y para mí ni siquiera es un problema – hacer un poco de abstracción nunca viene mal. No obstante, el autor ha reescrito todo ese código de ejemplo basándose en datos de prueba y la API de Spotify (que al fin y al cabo es el sustituto de Groove), respectivamente. El código fuente actualizado puede descargarse desde el blog de Joe Mayo.

Patrones en C# 7.0

“Early morning, April four
Shot rings out in the Memphis sky.
Free at last, they took your life
They could not take your pride.”
U2, “Pride (In the Name of Love)” (1984)

Otra de las novedades relevantes que introdujo C# 7.0 son los llamados patrones (patterns). Los patrones permiten, con una sintaxis concisa, comprobar que el valor de una expresión tiene una cierta “forma” y, en caso afirmativo, extraer información de esa expresión. El término “patrón” hace referencia al concepto de casamientoemparejamiento de patrones (pattern matching) tan popular en los lenguajes de programación lógica y funcional; aunque, como intentamos mostrar en un artículo anterior dedicado a la deconstrucción, por el momento las posibilidades que ofrece C# en esta área palidecen con respecto a las de tales lenguajes1.

En C# 7.0, los patrones pueden utilizarse para realzar la expresividad de dos construcciones del lenguaje:

  • El operador is de comprobación de tipos acepta ahora también en su parte derecha un patrón, además de un tipo.
  • Las cláusulas case de la sentencia switch ahora pueden especificar un patrón de emparejamiento en lugar de una constante.

Para los ejemplos de esta entrada nos centraremos en los nuevos usos del operador is; dejaremos los ejemplos de patrones en cláusulas case para nuestras próximas entregas, que estarán dedicadas íntegramente a las nuevas posibilidades que desde C# 7.0 ofrece la sentencia switch.

C# 7.0 incorpora tres tipos de patrones:

a) Los patrones constantes tienen la forma c, donde c es una constante, y comprueban que el valor de la expresión es igual a c. Por ejemplo:

    public static void Patterns1(object oint n)
    {
        if (o is null)
            Console.WriteLine("o is null");
        if (n is 27)
            Console.WriteLine("n is 27");
    }

El argumento de que is es más legible que == no es razón suficiente para convencerme a utilizar este tipo de patrón con el operador is. Tenga en cuenta el lector que la generación de código actual se apoya ciegamente en object.Equals() para la comparación, lo que puede dar lugar a pérdidas de rendimiento (por ejemplo, esto provoca dos operaciones de boxing al ejecutar la segunda sentencia if).

b) Los patrones de tipo tienen la forma T x, donde T es un tipo y x un identificador de variable; mediante ellos se comprueba si el valor de la expresión es de tipo T, y en caso afirmativo, se asigna dicho valor a la variable x de tipo T.

    public static void Patterns2(object o)
    {
        if (o is int n || (o is string s && int.TryParse(sout n)))
            Console.WriteLine(n);
        if (o is Person p)
            Console.WriteLine(p.FullName);
    }

Aquí si hay una economía de expresión importante sin comprometer la legibilidad, en mi humilde opinión. La primera parte del método del ejemplo imprime un valor en la consola si el parámetro suministrado es un entero (en cuyo caso, como “efecto colateral”, el valor entero es asignado a la variable n), o si el parámetro es una cadena que puede interpretarse como un entero (en cuyo caso, la cadena es asignada a la variable s, que es pasada a continuación a TryParse)2.

c) Finalmente, los patrones var tienen la forma var x, donde x es un identificador de variable; estos patrones casan incondicionalmente, y simplemente asignan el valor de la expresión a la variable x, que será del mismo tipo de la expresión.

    public static void Patterns3(object o)
    {
        if (o is var x)
            Console.WriteLine(x.GetType());
    }

Un caso particular a tener muy en cuenta al utilizar esta construcción es que en ella el patrón siempre casa, incluso si la expresión es null. Si el parámetro de entrada es null, el método del ejemplo lanzará NullReferenceException al intentar llamar a GetType(). No se me ocurre ninguna situación práctica en la que sea evidente la conveniencia de utilizar este tipo de patrón con el operador is; sin embargo, en una próxima entrega veremos un posible caso de uso, en combinación con la nueva cláusula when en la sentencia switch.

En las próximas entregas, que estarán dedicadas a la sentencia switch, arrojaremos más luz sobre el uso de estos tres tipos de patrones en las cláusulas case de la misma.

1 Los creadores de C# han prometido en varios foros que en el futuro se añadirán más tipos de patrones al lenguaje.

Nótese que la variable n se considera declarada tras su primera aparición, por lo que puede utilizarse como parámetro de salida de TryParse.


Referencia musical: Como esta entrada se escribe durante un día festivo dedicado a la figura de Martin Luther King, aprovecho para rememorar este tema dedicado a él de una banda imprescindible de las últimas cuatro décadas, U2,  que periódicamente nos demuestra que en el rock no todo son excesos e individualismo, sino que en ocasiones también puede encontrarse progresismo y compromiso social.

Empiece a programar. Un enfoque multiparadigma con C#

A tiempo para Noche Vieja me ha llegado mi copia del recién-publicado “Empiece a programar: un enfoque multiparadigma con C#“, escrito por mi maestro y amigo Miguel Katrib y otros miembros de su equipo. Es, sinceramente, el libro que me gustaría tener a mi alcance si volviera a tener 18 años y empezara mi andadura en el mundo de la programación. Y siendo totalmente honesto, debo reconocer también que aprendí un par de cosas y rememoré muchas otras con la lectura del libro, a pesar de mis 30+ años de experiencia y casi 20 usando C#. No me extiendo más aquí, porque el lector interesado podrá encontrar mi opinión detallada en el Prefacio del libro, junto con la Tabla de Contenidos, disponibles en la página del libro en Amazon.com.

¡Feliz 2018!

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

“When the hills of Los Angeles are burning
Palm trees are candles in the murder wind
So many lives are on the breeze
Even the stars are ill at ease
And Los Angeles is burning…”
Bad Religion, Los Angeles is Burning (2004)

Parte 1
Parte 2
Parte 3

Para concluir la serie sobre la implementación interna de las tuplas-valor, hoy presento un pequeño divertimento que vuelve a incidir sobre los detalles de dicha implementación.

Suponga que tiene una tupla-valor con varios elementos, y quiere tratar a dichos elementos como una secuencia, por ejemplo para agregarlos de alguna manera (recuerde que el operador Aggregate de LINQ no está limitado, ni mucho menos,  a secuencias numéricas). Independientemente de que LINQ ya hace tiempo que dejó estar en el centro de atención para convertirse en algo “normal”, estamos en presencia de un caso típico en el que quisiéramos “habilitar para LINQ” un tipo de datos específico, para lo que tan solo hace falta crear un método extensor que reciba una instancia del tipo y produzca a partir de ella un IEnumerable o IEnumerable<T> a recorrer. Es un tema al que dediqué varios artículos en la pasada década, en particular éste. Con un método como el anterior (al que llamaremos, como se recomienda, AsEnumerable), podremos hacer cosas como lo siguiente:

using System;
using System.Linq;
 
namespace ValueTuples
{
    class MainClass
    {
        static void Main()
        {
            var tuple = (12345678910);
            var sum = (from int x in tuple.AsEnumerable()
                       where x % 2 == 0
                       select x).Sum();              
            Console.WriteLine(sum); // Imprime 30
        }
    }
}

La implementación del método extensor que se me ha ocurrido (teniendo en cuenta las cosas que hemos venido hablando sobre la implementación interna de las tuplas-valor) es la que sigue a continuación; se aceptan sugerencias para su mejora.

using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using SC = System.StringComparison;
 
namespace ValueTuples
{
    public static class ValueTupleExtensions
    {
        public static IEnumerable AsEnumerable(this object valueTuple)
        {
            Type type = valueTuple.GetType();
            if (!type.FullName.StartsWith("System.ValueTuple"                                          SC.InvariantCulture))
                throw new InvalidOperationException("Invalid type");
            foreach (FieldInfo fi in type.GetFields().OrderBy(fi => fi.Name))
            {
                if (fi.Name == "Rest"// "El octavo pasajero"
                {
                    foreach (object o in AsEnumerable(fi.GetValue(valueTuple)))
                        yield return o;
                }
                else
                {
                    yield return fi.GetValue(valueTuple);
                }
            }
        }
    }
}

Referencia musical: Como el tiempo para concebir y escribir este post se presentó debido a un incendio forestal que amenazó nuestra empresa hoy hasta el punto de que nos evacuaron a casa, lo menos que pude hacer fue buscar una canción alusiva al tema, y creo que encontré una bastante buena. Bad Religion es una banda de punk-rock formada en Los Ángeles en 1980 (ya yo me estaba haciendo viejo por esa época) y que aún se mantiene activa. No soy un experto en su obra, pero suenan bien y tienen letras inteligentes.

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.