Sobre stackalloc, o (mejor) Homenaje a John Horton Conway

El pasado fin de semana estaba empezando a escribir este artículo al mismo tiempo que miraba las noticias, que últimamente no traen nada bueno. Ahora que se me van acabando las novedades recientes de C# de las que no he hablado todavía, y que no me atrevo a decir nada sobre aquella de la que no me siento preparado para hablar – léase los tipos-referencia anulables (nullable reference types), el artículo iba a estar dedicado al operador stackalloc, que con la última versión del lenguaje ha salido del oscuro nicho del código no seguro (unsafe code) para pasar a poder ser utilizado en escenarios más comunes.

Esencialmente, al utilizar stackalloc en lugar de new al inicializar un array local dentro de un método, se le está indicando al compilador que reserve la memoria para ese array en la pila de ejecución en lugar de crear un objeto heredero de Array en la memoria dinámica, como ocurriría al usar new. En C# 8.0, ese vector en la pila debe manipularse (fuera de un contexto no seguro) a través de la clase Span<T> de la que ya hablamos hace un tiempo aquí. En muy diversas situaciones, el alojamiento en la pila podría producir mejoras en el rendimiento, ya sea debido a la mayor velocidad de adquisición de la memoria (que en el caso de la pila se reduce a mover un puntero), a la localidad de los datos que el método manipula, o a la disminución en las necesidades de ejecución del recolector de basura.

Una vez claros los fundamentos teóricos en los que se basa la característica, tocaba lo más difícil: encontrar un buen ejemplo que hiciera evidente esas ventajas. Y entonces levanté los ojos para mirar las noticias, donde presentaban a personajes más o menos conocidos que perdieron la vida recientemente a causa del brutal COVID-19. Entre ellos estaba John Horton Conway, el genial matemático británico que creó esa maravilla de máquina universal de Turing que es el Juego de la vida (Game of Life). El Juego de la vida forma parte indeleble de mis memorias de juventud: aún recuerdo el brillo en los ojos de nuestros alumnos de Programación en la Universidad de La Habana cuando les explicábamos1 en qué consiste el juego y luego los veíamos pasarse horas implementándolo en aquellos IBM PC originales (para los que la fecha inicial siempre era el 1 de enero de 1980), utilizando esa otra maravilla de la tecnología de su tiempo que era Turbo PASCAL 3.0 (¿alguien recuerda el número 39671?).

El fichero que se adjunta al final del artículo contiene una implementación en C# del Juego de la vida, adaptada de la que se ofrece en Rosetta Code. El método que crea una nueva generación a partir de la actual es el siguiente:

private void UpdateBoard()
{
    // A temp variable to hold the next state while it's being calculated.
    Span<bool> newBoard = new bool[Width * Height];

    for (var y = 0; y < Height; y++)
    {
        for (var x = 0; x < Width; x++)
        {
            var n = CountLiveNeighbors(x, y);
            var c = board[x + y * Width];

            // A live cell dies unless it has exactly 2 or 3 live neighbors.
            // A dead cell remains dead unless it has exactly 3 live neighbors.
            newBoard[x + y * Width] = c && (n == 2 || n == 3) || !c && n == 3;
        }
    }

    // Set the board to its new state.
    board = newBoard.ToArray();
}

Para adaptarme a las limitaciones de stackalloc, he introducido a Span<T>, he convertido a posta la estructura de datos newBoard (originalmente una matriz) en un array, y me he echado encima el cálculo de la posición en memoria de los elementos. Todo eso hace posible pasar de una versión que usa new para reservar la memoria a una que utiliza stackalloc cambiando sólo la primera línea de código del método:

    Span<bool> newBoard = stackalloc bool[Width * Height];

Con el objetivo de comparar el rendimiento de ambas variantes, el programa de ejemplo ejecuta bajo condiciones repetibles 250 juegos sobre un tablero de 100*100 celdas. Invito al lector a que repita el experimento y vea si obtiene resultados diferentes al mío. En mi viejo Mac, con la optimización de código activada y ejecutando sin depuración, la variante que usa new requirió 694 segundos, mientras que la variante basada en stackalloc consumió 676. No puedo asegurar que estos números den una respuesta concluyente sobre las ventajas de usar stackalloc, pero eso lo dejo también al análisis del lector interesado. A fin de cuentas, el objetivo de este artículo no era tanto hablar de programación como servir como modesto homenaje a la vida y obra de John Horton Conway. God rest his soul!


1 Me refiero aquí a los miembros del equipo docente dirigido por mentor y amigo Miguel Katrib.

Código fuente:

using System;

namespace Conway
{
    // Plays Conway's Game of Life with a random initial state.
    public class GameOfLifeBoard
    {
        // The dimensions of the board in cells.
        private const int Width = 100;
        private const int Height = 100;

        // Holds the current state of the board.
        private bool[] board;

        // Creates the initial board with a random state.
        public GameOfLifeBoard(int randomSeed = 0)
        {
            var random = randomSeed == 0 ? new Random() : new Random(randomSeed);

            board = new bool[Width * Height];
            for (var y = 0; y < Height; y++)
            {
                for (var x = 0; x < Width; x++)
                {
                    // Equal probability of being true or false.
                    board[x + y * Width] = random.Next(2) == 0;
                }
            }
        }

        // Play the game until the colony extinguishes or maximum iterations is reached
        public bool Run(int maxIterations)
        {
            int i = 0;
            while (i < maxIterations & !IsEmpty)
            {
                UpdateBoard();
                i++;
            }
            return !IsEmpty;
        }

        // Return whether the colony has no live cells
        public bool IsEmpty
        {
            get
            {
                for (var y = 0; y < Height; y++)
                {
                    for (var x = 0; x < Width; x++)
                    {
                        if (board[x + y * Width])
                        {
                            return false;
                        }
                    }
                }
                return true;
            }
        }

        // Moves the board to the next state based on Conway's rules.
        private void UpdateBoard()
        {
            // A temp variable to hold the next state while it's being calculated.
            Span newBoard = new bool[Width * Height];

            for (var y = 0; y < Height; y++)
            {
                for (var x = 0; x < Width; x++)
                {
                    var n = CountLiveNeighbors(x, y);
                    var c = board[x + y * Width];

                    // A live cell dies unless it has exactly 2 or 3 live neighbors.
                    // A dead cell remains dead unless it has exactly 3 live neighbors.
                    newBoard[x + y * Width] = c && (n == 2 || n == 3) || !c && n == 3;
                }
            }

            // Set the board to its new state.
            board = newBoard.ToArray();
        }

        // Returns the number of live neighbors around the cell at position (x,y).
        private int CountLiveNeighbors(int x, int y)
        {
            // The number of live neighbors.
            int value = 0;

            // This nested loop enumerates the 9 cells in the specified cells neighborhood.
            for (var j = -1; j <= 1; j++)
            {
                // If y+j is off the board, continue.
                if (y + j < 0 || y + j >= Height)
                {
                    continue;
                }

                for (var i = -1; i <= 1; i++)
                {
                    // If x+i is off the board, continue.
                    if (x + i < 0 || x + i >= Width)
                    {
                        continue;
                    }

                    // Count the neighbor cell at (h,k) if it is alive.
                    value += board[x + i + (y + j) * Width] ? 1 : 0;
                }
            }

            // Subtract 1 if (x,y) is alive since we counted it as a neighbor.
            return value - (board[x + y * Width] ? 1 : 0);
        }

        static void Main(string[] args)
        {
            const int MaxGames = 250;
            const int MaxIterations = 10000;

            DateTime start = DateTime.Now;
            int survived = 0, extinguished = 0;
            for (int i = 0; i < MaxGames; i++)
            {
                Console.WriteLine(i);
                var board = new GameOfLifeBoard(i);
                if (board.Run(MaxIterations))
                {
                    survived++;
                }
                else
                {
                    extinguished++;
                }
            }
            DateTime end = DateTime.Now;

            Console.WriteLine($"Survived:     {survived,6:d}");
            Console.WriteLine($"Extinguished: {extinguished,6:d}");
            Console.WriteLine($"Total time:   {(int)(end - start).TotalSeconds,6:d}");
        }
    }
}

C# 8.0: Enhancements in pattern matching (II)

You can find the original (Spanish) version of this post here.

“Button up your overcoat
When the wind is free
Take good care of yourself
You belong to me!”
Frank Sinatra, “Pick Yourself Up”

In the very first place, I would like to wish all my readers that the perfidious COVID-19 leaves you unscathed, and to encourage you to take care of yourselves and your loved ones – there is so much future ahead of us once we have beaten the obnoxious bug!

In the previous post, where we talked about the enhancements added to the 8.0 version of C# in connection with pattern matching, we left pending a presentation of the new positional patterns; we will try to address that omission here.

Positional patterns allow us to leverage the deconstruction mechanism introduced in C# 7.0 (we talked about it quite some time ago here) to create recursive patterns that combine a type pattern with one or more internal patterns so that the results of the deconstruction of the object subject to matching are recursively matched one by one against those internal patterns. Notice that in C# 7.x, deconstruction could not be used in pattern matching scenarios.

Continuing with the example of the previous post, let’s assume that the Address class includes the following deconstruction mechanism:

public static class AddressExtensions
{
    public static void Deconstruct(this Address address, 
        out string address1, out string address2, out string city, 
        out string state, out string zipCode, out string country)
    {
        address1 = address.StreetAddress;
        address2 = address.StreetAddress2;
        city = address.City;
        state = address.State;
        zipCode = address.ZipCode;
        country = address.Country;
    }
}

Then the method to determine whether a certain address belongs to the Los Angeles downtown, which last time we implemented like this (using a tuple pattern):

public static bool IsDowntownLA(this Address address)
{
    return (address.City, address.ZipCode) is ("Los Angeles", "90012");
}

Could now be also written like this:

public static bool IsDowntownLA_v2(this Address address)
{
    return address is Address(_, _, "Los Angeles", "CA", "90012", _);
}

With the positional patterns and their support for recursion, Microsoft has solved most of the «shallowness» problems in the C# 7.0 pattern matching of which I had complained in the aforementioned post. If, for instance, the City property of Address were an object (with its corresponding deconstructor defined) instead of a simple character string, we would be able to supply a type pattern in the corresponding position, and it would be recursively matched:

public class City
{
    public string Name { getprivate set; }
    public int Population { getprivate set; }

    public City(string name, int population)
    {
        Name = name;
        Population = population;
    }
}
public static class CityExtensions
{
    public static void Deconstruct(this City city,
        out string name, out int population)
    {
        name = city.Name;
        population = city.Population;
    }

    public static bool IsDowntownLA_v3(this Address address)
    {
        return address is Address(_,_, City("Los Angeles",_), "CA", "90012", _);
    }
}

This already looks pretty similar to Prolog’s unification! (although, of course, just in one direction ;-)).

Surely the reader will agree with me in that the original version of IsDowntownLA looks better than the newer ones. The former is more readable, and does not have such as strong dependence on the order (position) of the deconstruction parameters as the other two versions do, which could probably be the source of some future maintenance headaches. But surely you will find scenarios where the associated risks are minimal. For instance, the Microsoft documentation uses in its code fragments the Point class, for which it’s obvious that the deconstruction parameters should be x, y, z (in that order).


Cultural reference: Both Frank Sinatra and Nat King Cole were frequently played in my house when I was a kid, and I’ve never been able to fully break their spell.

C# 8.0: Mejoras en el emparejamiento de patrones (y II)

“Button up your overcoat
When the wind is free
Take good care of yourself
You belong to me!”
Frank Sinatra, “Pick Yourself Up”

Ante todo, quiero desearle a todos mis lectores que el pérfido COVID-19 pase de largo sin tocarles, y exhortarlos a que se cuiden y cuiden a los suyos: ¡hay mucho futuro por delante después que hayamos vencido al odioso bichito!

De la entrada anterior, donde hablamos sobre las mejoras introducidas por la versión 8.0 de C# en relación con el emparejamiento de patrones (pattern matching), nos quedó pendiente la presentación de los patrones posicionales, omisión que intentaremos subsanar ahora.

Los patrones posicionales permiten utilizar el mecanismo de deconstrucción introducido en C# 7.0 (del que hablamos hace algún tiempo aquí) para expresar patrones recursivos que combinen un patrón de emparejamiento por el tipo de datos con uno o más patrones internos en los que se «casen» uno a uno los resultados de la deconstrucción del objeto sujeto a emparejamiento. En C# 7.x, la deconstrucción no se puede utilizar en escenarios de emparejamiento de patrones.

Continuando con el ejemplo de la entrega anterior, supongamos que la clase Address incluye el siguiente mecanismo de deconstrucción:

public static class AddressExtensions
{
    public static void Deconstruct(this Address address, 
        out string address1, out string address2, out string city, 
        out string state, out string zipCode, out string country)
    {
        address1 = address.StreetAddress;
        address2 = address.StreetAddress2;
        city = address.City;
        state = address.State;
        zipCode = address.ZipCode;
        country = address.Country;
    }
}

Entonces el método para determinar si una cierta dirección pertenece al downtown de la ciudad de Los Ángeles, que la vez anterior escribimos así (usando un patrón de tupla):

public static bool IsDowntownLA(this Address address)
{
    return (address.City, address.ZipCode) is ("Los Angeles", "90012");
}

Podríamos también haberlo escrito así:

public static bool IsDowntownLA_v2(this Address address)
{
    return address is Address(_, _, "Los Angeles", "CA", "90012", _);
}

Con los patrones posicionales y recursivos, Microsoft resuelve muchos de los problemas de «superficialidad» en el emparejamiento de patrones en C# 7.0 de los que me quejaba en el artículo antes mencionado. Si, por ejemplo, la propiedad State de Address fuera un objeto compuesto en lugar de una simple cadena de caracteres, se podría especificar un objeto-patrón en la posición correspondiente, y éste sería emparejado recursivamente. ¡Esto ya se va pareciendo bastante a la unificación (unification) de Prolog (aunque, claro, en una sola dirección ;-))!

Por supuesto, el lector coincidirá conmigo en que la versión original de IsDowntownLA es mejor que la nueva. Aquélla es más legible, y ésta tiene una dependencia directa del orden (o posición) de los parámetros de la deconstrucción, cosa que puede darnos algún que otro dolor de cabeza de mantenimiento. Pero seguramente pueden encontrarse escenarios en los que estos riesgos sean mínimos. Por ejemplo, la documentación de Microsoft utiliza en sus fragmentos de código la clase Point, donde es obvio que los parámetros debe ser x, y, z (en ese orden).


Referencia musical: Tanto Frank Sinatra como Nat King Cole se oían muy frecuentemente en mi casa cuando era niño, y aún no he podido librarme totalmente de su embrujo.

C# 8.0: Mejoras en el emparejamiento de patrones

Hace un par de años, en un artículo publicado en los tiempos de C# 7.x, presentamos los conceptos fundamentales en relación con el emparejamiento de patrones (pattern matching), característica que recién entonces se incorporaba a C# luego de su probado éxito en diferentes lenguajes de programación funcional y lógica (aquí no puedo evitar pensar en Prolog y en quien me lo enseñó, mi querido maestro Dr. Luciano García Garrido, que aún sigue formando profesionales en la Universidad de la Habana). La versión 8.0 de C# ha añadido algunas mejoras con relación a lo que en aquel momento se presentaba como gran novedad; sobre esas mejoras trata precisamente este artículo.

Como bien puede leerse en la página oficial de Microsoft relacionada con las novedades de C# 8.0, el emparejamiento de patrones nos da la posibilidad de expresar funcionalidades dependientes de la forma de los datos; como tal, se trata de un caso particular de programación dirigida por los datos (data-driven programming). La versión 7.0 de C# añadió sintaxis para expresar tres tipos de patrones: los patrones constantes, los patrones de tipo y los patrones var. En un siguiente paso de refinamiento, C# 8.0 ha expandido su vocabulario con nuevos tipos de patrones; además, los patrones, que antes podían utilizarse en dos escenarios, las expresiones is y las sentencias switch, ahora también pueden utilizarse en un tercer contexto: las expresiones switch, de las que ya hablamos aquí. En mi modesta opinión, la referida página de Microsoft no hace un buen trabajo al separar los patrones en sí de los contextos en que pueden utilizarse, cosa que podría llevar a un principiante a confundir los frijoles con la cazuela en que éstos se cuecen ;-). Además, en todos los ejemplos el artículo utiliza expresiones switch; razón por la cual presentaré aquí los nuevos patrones utilizando las otras construcciones.

Los nuevos patrones disponibles en C# 8.0 son los siguientes:

  • Patrones de propiedad: Ahora es posible emparejar una expresión con un patrón usando una propiedad de la expresión. Por ejemplo, si se tiene una clase Address para representar direcciones de correo postal tradicionales, se podría crear el siguiente método para determinar si una dirección corresponde a uno de los estados de la costa oeste de los Estados Unidos:
public static bool IsWestCoast(this Address address)
{
    switch (address)
    {
        case { State: "CA" }: return true;
        case { State: "OR" }: return true;
        case { State: "WA" }: return true;
        default:              return false; 
    }
}
  • Patrones de tupla: Ahora es posible también emparejar una tupla, miembro a miembro, con un patrón de tupla que tenga la misma cantidad de elementos. Este tipo de patrón es útil cuando un algoritmo necesita «casar» varias entradas simultáneamente. Por ejemplo, el siguiente método utiliza una expresión is para determinar si una cierta dirección pertenece al downtown de la ciudad de Los Ángeles:
public static bool IsDowntownLA(this Address address)
{
    return (address.City, address.ZipCode) is ("Los Angeles", "90012");
}

Aunque la documentación oficial a la que antes hacíamos referencia los presenta de manera separada, los patrones de tupla podrían considerarse como un caso particular de los patrones posicionales que se mencionan a continuación.

  • Patrones posicionales: Dejaremos la presentación de los patrones posicionales para la próxima entrega, dado que ésta ya se va haciendo demasiado larga.
  • Por último, vale la pena mencionar que en C# 8.0 los patrones pueden combinarse de manera recursiva. Según la página de Microsoft, «un patrón recursivo es simplemente una expresión-patrón aplicada al resultado producido por otra expresión-patrón». Por ejemplo, suponga que tuviéramos las clases ResidentialAddress y CommercialAddress, derivadas de Address, y quisiéramos definir un método IsCaliforniaBusiness(Address address) para calcular si una dirección corresponde a un negocio o local comercial localizado en California. La versión que se muestra a continuación satisface los requerimientos. En ella primeramente se hace un emparejamiento por el tipo de datos (disponible desde C# 7.0), para luego aplicar un emparejamiento por la propiedad State.
public static bool IsCaliforniaBusiness(this Address address)
{
    return address is CommercialAddress { State: "CA" };
}

15/03/2020: Puede leer la continuación aquí.

C# 8.0: Secuencias asíncronas

Antes que nada, aprovecho para felicitar al lector por la llegada del nuevo año. ¡Le deseo muchas cosas buenas en 2020!

Me permito esta vez alterar lo que sería un orden más natural de presentación de las nuevas características añadidas a C# 8.0 y saltar directamente a una de las más avanzadas, las secuencias asíncronas (async enumerables). Lo hago principalmente como modesto homenaje al cierre de MSDN Magazine, que en su ejemplar final incluye un excelente artículo dedicado al tema, «Iterating with Async Enumerables in C# 8» , de Stephen Toub, disponible aquí. En esta entrega intentaré hacer lo que siempre: no repetir, sino intentar una presentación didáctica y un ejemplo claro y sencillo de utilización de la tecnología en cuestión que complemente a los que pueden encontrarse en las fuentes oficiales. Para acceder a los detalles más técnicos, remito al lector al artículo antes mencionado o a la descripción online de la característica en docs.microsoft.com, que parte de aquí.

En esencia, las secuencias asíncronas hacen posible que un código cliente recorra los elementos de una secuencia utilizando llamadas asíncronas a la hora de producir los elementos de la misma, de modo que el hilo que lleva a cabo el recorrido (enumeración) no quede bloqueado mientras espera por un elemento y pueda llevar a cabo algún otro trabajo útil. Esta posibilidad adquiere una utilidad especial cuando la secuencia a recorrer no es estática o está generada de antemano, sino que se compone dinámicamente a partir de elementos que provienen de una fuente externa, como ocurre en el ejemplo que presentaremos a continuación.

Para hacer posible la implementación de las secuencias asíncronas, .NET Core 3.0 introduce las nuevas interfaces IAsyncEnumerable<T> e IAsyncEnumerable<T> en el espacio de nombres System.Collections.Generic, y la interfaz IAsyncDisposable en el espacio de nombres System. Estas interfaces guardan mucha semejanza con  sus homólogas en el mundo síncrono:

namespace System.Collections.Generic
{
    public interface IAsyncEnumerable<out T>
    {
        IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken ct = default);
    }

    public interface IAsyncEnumerator<out T>
    {
        ValueTask<bool> MoveNextAsync();
        T Current { get; }
    }
}

namespace System
{
    public interface IAsyncDisposable
    {
        ValueTask DisposeAsync();
    }
}

Observe que la interfaz de enumerador asíncrono no tiene equivalente para el método Reset() de IEnumerator, que ha sido marcado como obsoleto en .NET Core 3.0. Sobre ValueTask y ValueTask<T> ya hablamos en un artículo anterior.

Para producir una secuencia asíncrona en C# 8.0, basta con utilizar la interfaz asíncrona necesaria en la firma del método. Para consumirla, se debe utilizar la construcción await foreach en vez de foreach:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
        static async IAsyncEnumerable<int> FibonacciAsync()
        {
            int n0 = 1, n1 = 1;
            while (true)
            {
                await Task.Delay(100);
                int n2 = n0 + n1;
                n0 = n1; n1 = n2;
                yield return n2;
            }
        }
        static async Task Main(string[] args)
        {
            await foreach(int f in FibonacciAsync())
            {
                Console.WriteLine(f);
                if (f > 100)
                    break;
            }
        }
    }
}

Como ya nos tiene acostumbrados, el compilador se encarga de generar la máquina de estados necesaria para generar la asincronía, y el try/finally (llamando a DisposeAsync, claro) alrededor del recorrido de la secuencia. Puede encontrar más detalles técnicos sobre todo ello en el artículo de Stephen Toub antes mencionado.

Para el ejemplo especial de hoy, debo remitirme a un artículo que escribí para MSDN hace mucho tiempo (febrero de 2008, para ser más exactos). El artículo, llamado «Applying LINQ to new data types«, y que ya solo está disponible aquí (¡gracias a mis buenos amigos de Plain Concepts por ello!), muestra cómo encapsular en un método cliente la recepción de una secuencia de mensajes provenientes de una fuente de comunicación interprocesos conocida como canalización con nombre (named pipe). La solución allí presentada, que se apoya en una secuencia síncrona, podría no ser eficiente, dado que los mensajes podrían llegar a intervalos de tiempo variables, y entonces el hilo que ejecuta el recorrido estaría completamente bloqueado durante esos intervalos esperando la llegada de un mensaje. El uso de una secuencia asíncrona podría venir aquí como anillo al dedo.

La conversión del método que produce los mensajes en un método asíncrono es bastante directa, gracias a que a estas alturas .NET ya está bien preparado para este tipo de programación; en particular, la clase NamedPipeClientStream ofrece variantes asíncronas para la conexión y lectura:

public static async IAsyncEnumerable GetMessagesAsync(
    this NamedPipeClientStream pipeStream)
{
    await pipeStream.ConnectAsync();
    pipeStream.ReadMode = PipeTransmissionMode.Message;

    Decoder decoder = Encoding.UTF8.GetDecoder();

    const int BufferSize = 256;
    byte[] bytes = new byte[BufferSize];
    char[] chars = new char[BufferSize];
    int nBytes = 0;
    StringBuilder msg = new StringBuilder();
    do
    {
        msg.Length = 0;
        do
        {
            nBytes = await pipeStream.ReadAsync(bytes, 0, BufferSize);
            if (nBytes > 0)
            {
                int nChars = decoder.GetCharCount(bytes, 0, nBytes);
                decoder.GetChars(bytes, 0, nBytes, chars, 0, false);
                msg.Append(chars, 0, nChars);
            }
        } while (nBytes > 0 && !pipeStream.IsMessageComplete);
        decoder.Reset();
        if (nBytes > 0)
        {
            // we've got a message - yield it!
            yield return msg.ToString();
        }
    } while (nBytes != 0);
}

El consumo de la secuencia asíncrona por el código cliente es también bastante inmediato:

static async Task Main(string[] args)
{
    const string Server = ".";
    const string PipeName = "CS3";
 
    using (NamedPipeClientStream pipeStream =
      new NamedPipeClientStream(Server, PipeName, PipeDirection.InOut))
    {
        await foreach (var s in pipeStream.GetMessagesAsync())
            Console.WriteLine(s);
    }
    Console.ReadLine();
}

Hasta aquí todo muy bien, pero para poder ir más allá hay un gran PERO: como bien explica Stephen Toub al final de su artículo, ninguno de los dos mecanismos de consultas integradas (LINQ) que ofrecen .NET Core 3.0 y C# 8.0 (los métodos extensores y la sintaxis de consulta) incluye soporte alguno para las secuencias asíncronas; para ello, por ahora, hay que acudir a la librería System.Linq.Async del proyecto github.com/dotnet/reactive. Así que le debo para la próxima, estimado lector, una reescritura del último ejemplo de mi artículo sobre las canalizaciones, en el que filtraba y ordenaba los mensajes recibidos mediante una consulta LINQ. ¡Hasta entonces!


Advertencia: Como casi siempre hago, comencé probando el código presentado aquí en mi Mac, al que tengo mucho apego. Viendo que las canalizaciones con nombre estaban soportadas en .NET Core 3.0, pensé que todo iría de maravillas; sin embargo, al intentar ejecutar el servidor obtuve el error «Message transmission mode is not supported on this platform«. Entonces tuve que cambiarme a Windows :-(.

Adiós a MSDN Magazine

“… When the light dies down and you blood runs cold
Then you know what you fear most is growing old…”
Kansas, “End of the Age” (1983)

Sorpresa y tristeza me embargaron a la vez al leer el editorial del que será el último ejemplar de MSDN Magazine. Sorpresa, porque no lo esperaba, y tristeza, casi tanta como la que sentí cuando publicamos el último ejemplar de dotNetManía a mediados de 2013. He leído MSDN Magazine (y sus predecesores) prácticamente desde que tengo «uso de razón» profesional. Y aunque está claro desde hace mucho que la prensa escrita tiene sus días contados, pensaba que los deep pockets de Microsoft nos permitirían seguir disfrutando de la revista aún por muchos años. Pero en los tiempos que corren la Inteligencia Artificial se aplica con ubicuidad, y particularmente a la hora de recortar al máximo los gastos superfluos o que no producen una ganancia evidente o inmediata.

Solo espero y deseo que Microsoft continúe publicando online artículos de la calidad de los que, por ejemplo, se incluyen en este último ejemplar.


Referencia musical: «Drastic Measures» (1983) es muy probablemente el peor álbum de toda la discografía de mi grupo favorito, Kansas. Tan malo, que su no aceptación provocó la desintegración del grupo (para luego reaparecer al cabo de unos años, con miembros y estilo algo diferentes). No obstante, le tengo cierto cariño a ese disco por la cantidad de años que tuve que esperar para oírlo. Es de 1983, una época en que el vinilo (parecía que) estaba muriendo y el CD no se había impuesto todavía. Solo después de llegado el nuevo milenio (aunque años antes de que el streaming comenzara a ser una alternativa) se decidió Sony Music a reeditar el disco en CD. «End of the Age«, a pesar del tono pesimista y las veladas referencias religiosas, es de lo poco del disco que puede tener algún valor (histórico al menos) para un fan de la banda.

C# 8.0: Expresiones switch

Otra de las adiciones a C# 8.0 que se extrañaban desde hace mucho tiempo y cuya llegada se agradece son las expresiones switch (switch expressions). Una buena parte de las sentencias switch que ponemos en nuestro código fuente tiene como único objetivo computar un valor en dependencia de otro, y el uso de una sentencia switch en tales casos (aunque siempre una mejora en legibilidad y elegancia en comparación con el uso de la cascada equivalente de sentencias ifelse) recuerda la verborrea innecesaria de muchos políticos.

Ya uno de los ancestros que más han influido en los lenguajes modernos, Algol 68 (definido en 1968, claro), incluía esta característica. Por ejemplo, observe cómo lucía en este lenguaje una función para determinar la cantidad de días en un mes (tomada de Wikipedia):

proc days in month = (int year, month) int:
  case month in
    31,
    if year mod 4 eq 0 and year mod 100 ne 0 or year mod 400 eq 0 then 29 else 28 fi,
    31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  esac;

Una versión parecida en C# 8.0 que utiliza una expresión switch podría ser la siguiente:

public static int DaysInMonth(int month, int year) => month switch
{
    4 => 30, 6 => 30, 9 => 30, 11 => 30,
    2 => (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 29 : 28,
    _ => 31,
};

Observe cómo en estas expresiones la palabra reservada switch se utiliza de manera infija entre la variable (o expresión) según cuyo valor se discrimina y el bloque que contiene la secuencia de pares de expresiones (valor => resultado), y cómo la palabra reservada default se sustituye convenientemente por el comodín _. El resultado de la expresión switch en su conjunto será el resultado de la evaluación de la parte derecha cuya parte izquierda «case» con el valor discriminante. Si en tiempo de ejecución se analiza toda la lista sin que se encuentre una parte izquierda coincidente, se lanzará una excepción de tipo SwitchExpressionException (espacio de nombres System.Runtime.CompilerServices). El compilador hará lo mejor que pueda para advertirle de antemano de tal posibilidad.

Como ya seguramente sabe el lector, la máxima expresividad al utilizar una expresión switch se obtiene cuando se la combina con el uso de patrones (patterns). Para leer más sobre los patrones que fueron incorporados al lenguaje en C# 7.x, puede consultar mis entradas anteriores Patrones en C# 7.0, La sentencia switch en C# 7.0 y La sentencia switch en C# 7.0 (y 2). Estos artículos le pueden servir de introducción a una próxima entrega que describirá los nuevos patrones incorporados a C# 8.0. Si no puede o no quiere esperar, le recomiendo sinceramente la serie de artículos sobre C# 8.0 que publicó en este mismo sitio mi colega Jorge Serrano.

C# 8.0: Índices y rangos

“Home, home on the range,
Where the deer and the antelope play…”
Canción folklórica del oeste nortemericano

Ahora que ya tenemos oficialmente disponibles .NET Core 3.0 y C# 8.0, podemos con total propiedad continuar la presentación de las nuevas características del lenguaje introducidas en C# 8.0 que habíamos comenzado unos meses atrás. En esta ocasión le toca el turno a los nuevos operadores de índice y rango, que ya describió perfectamente en su blog mi buen amigo Jorge Serrano aquí.

En esencia, se trata de dos nuevos operadores de «azúcar sintáctico» (aunque no se si se debería seguir usando ese término en estos tiempos en que el azúcar parece ser el causante de todos los males de la humanidad :-)) que simplifican y hacen más legible la selección de un elemento o de un subconjunto de elementos de un array. Estos operadores se apoyan en dos nuevos tipos añadidos a .NET Core 3.0, System.Index y System.Range.

El operador unario ^ permite hacer referencia a un elemento contando a partir del final. Por ejemplo, en el siguiente código la función IsPalindromic permite determinar si un array es palindrómico (o sea, que se lee igualmente de atrás hacia delante que de delante hacia atrás):

using System;

namespace IndexRange
{
   class Program
   {
      static int[] data = { 1, 2, 7 /* 3 */, 4, 5, 4, 3, 2, 1 };

      static bool IsPalindromic(T[] array) where T: IComparable
      {
         for (int i = 1; i <= array.Length / 2; i++)
            if (array[i - 1].CompareTo(array[^i]) != 0)
               return false;
         return true;
      }

      static void Main(string[] args)
      {
         Console.WriteLine(IsPalindromic(data));
      }
   }
}

Observe que ^0 apunta al final del array (o sea, la posición inmediatamente detrás del último elemento); el índice correspondiente al último elemento (el primero desde atrás) es ^1. Lo cierto es que yo aún ando buscando un buen ejemplo de uso de esta nueva característica; no creo que vaya a usarla mucho en el futuro.

Lo que sí me gustó desde que los vi fueron los rangos, y los vi por primera vez cuando aparecieron en Swift, el lenguaje de Apple. Tanto me gustaron que decidí clonar el repositorio de código de Roslyn e implementar la característica yo mismo, en parte siguiendo las orientaciones en la página de Github asociada; un ejercicio altamente productivo que recomiendo a todo el que esté realmente interesado en las interioridades de un compilador moderno.

El operador de rango .. es un operador binario infijo, que produce un valor de tipo System.Range a partir de dos System.Index, aunque ambos operandos pueden ser opcionales; los ejemplos de código a continuación ilustran los diferentes escenarios . Este operador hace posible referirse mediante una sintaxis intuitiva a un subconjunto de los elementos de un array.

      // ...

      static void Print(T[] array)
      {
         foreach (var t in array)
            Console.Write($"{t} ");
         Console.WriteLine();
      }

      static void Main(string[] args)
      {
         // ...
         int m = 2, n = 4;
         Print(data[m..n]);
         Print(data[2..^3]); // data[Range.Create(2, Index.CreateFromEnd(3))]
         Print(data[..^3]);  // data[Range.ToEnd(Index.CreateFromEnd(3))]
         Print(data[2..]);   // data[Range.FromStart(2)]
         Print(data[..]);    // data[Range.All]
      }

La salida que produce el código anterior es:

7 4
7 4 5 4
1 2 7 4 5 4
7 4 5 4 3 2 1
1 2 7 4 5 4 3 2 1

Observe que el extremo inferior de los rangos es inclusivo, y el superior exclusivo. Eso es consistente, en los casos en que se omite el segundo operando, con la noción de que la primera posición desde atrás está precisamente más allá del final del array.


Referencia musical: Bueno, reconozco que esta vez se me ha «ido la mano» buscando una referencia musical en la que se usara el término range. Se trata de una palabra polisémica, que puede utilizarse para describir un conjunto de elementos de una misma naturaleza, pero también una sierra o cordillera de montañas; éste último significado es el que se utiliza en la canción mencionada. Mi grupo favorito, Kansas, incluyó una versión de este clásico en las pistas adicionales de su último disco, «The Prelude Implicit» (2016).

Mi agradecimiento a este simpático animalito por la inspiración:
Deer behind home

C# 8.0: Miembros implementados por defecto en interfaces

Casi terminado ya (al menos oficialmente) el verano, continúo con la descripción de las nuevas características que se añadirán a C# 8.0. Esta entrada ya llevaba más de un mes «en el tintero», pero para las próximas intentaré adaptarme al ritmo y contenidos de la serie que irá publicando mi buen amigo Jorge Serrano, tratando de no repetir, sino más bien de complementar lo que él presente allí.

Esta vez le tocó el turno a los miembros implementados por defecto en interfaces (default interface members). Esta característica, con toda seguridad una de las adiciones más importantes a C# 8.0, hace posible que el creador de una librería pueda añadir métodos, propiedades, etc. a una interfaz en versiones futuras sin romper la compatibilidad a nivel binario o de código fuente con las implementaciones de la interfaz que ya pudieran existir. Otros lenguajes modernos, como Java y Swift, ofrecen desde hace algún tiempo características similares.

Suponga que tenemos un sistema que parte de una librería básica en la que se define una interfaz IProduct para representar productos que se ponen a la venta en un momento cualquiera. La interfaz declara miembros que corresponden a las diferentes características de los productos, tales como marca, modelo, fabricante, país de fabricación y precio base, y a las funcionalidades requeridas, en este caso un método para calcular el precio de venta (sale price) de un producto. La fórmula para calcular el precio de venta, en el caso general, puede variar ampliamente; no se calcula de la misma manera en una tienda «Todo a cien» que en un establecimiento perteneciente a una gran cadena.

using System;

namespace DefaultInterfaceMemberImplementations
{
    public enum ManufacturingCountry
    {
        USA,
        Canada,
        UnitedKingdom,
        Germany,
        China,
        Japan,
        SouthKorea
    }

    public interface IProduct
    {
        string Name { get; }
        string Model { get; }
        string Manufacturer { get; }
        ManufacturingCountry ManufacturingCountry { get; }
        decimal BasePrice { get; }

        decimal GetSalePrice();
    }

Una tienda de electrodomésticos (appliances) que hiciera uso de la librería anterior podría apoyarse en ella para definir sus tipos y funcionalidades de la siguiente forma:

    public interface IAppliance : IProduct
    {
        ApplianceCategory ApplianceCategory { get; }
    }

    public enum ApplianceCategory
    {
        Televisor,
        Refrigerator,
        DishWasher,
        Washer,
        Dryer,
    }

    public abstract class ApplianceBase : IAppliance
    {
        public const decimal AppliancesMarkup = 0.1M;  // 10%

        public ApplianceCategory ApplianceCategory { getprivate set; }
        public string Name { getprivate set; }
        public string Model { getprivate set; }
        public string Manufacturer { getprivate set; }
        public ManufacturingCountry ManufacturingCountry { getprivate set; }
        public decimal BasePrice { getprivate set; }
        protected ApplianceBase(ApplianceCategory category,
            string name, string model, string manufacturer,
            ManufacturingCountry country, decimal basePrice)
        {
            ApplianceCategory = category;
            Name = name;
            Model = model;
            Manufacturer = manufacturer;
            ManufacturingCountry = country;
            BasePrice = basePrice;
        }

        public virtual decimal GetSalePrice() => 
            (1M + AppliancesMarkup) * BasePrice;
    }

    public class Televisor : ApplianceBase
    {
        public Televisor(string name, string model, string company, 
            ManufacturingCountry country, decimal basePrice) : 
            base(ApplianceCategory.Televisor, 
                name, model, company, country, basePrice) { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var sms = new Televisor("4K TV", "QN55Q8FN", "Samsung", 
                ManufacturingCountry.SouthKorea, 1265M);
            Console.WriteLine($"Samsung price = {sms.GetSalePrice():N2}");
            var tcl = new Televisor("4K TV", "55R617", "TCL", 
                ManufacturingCountry.China, 529M);
            Console.WriteLine($"TCL price = {tcl.GetSalePrice():N2}");
        }
    }
}

En el código anterior, a los electrodomésticos se les marca con un 10% adicional.

Imagine ahora que el contexto internacional cambia y a un cierto personaje se le ocurre empezar a imponer tarifas de importación a los productos provenientes de países que no se plieguen a sus exigencias. En tal caso, se hará necesario subir los precios de venta a los productos para compensar la pérdida de beneficios causados por las tarifas. Sería conveniente extender la interfaz IProduct con un método para determinar la tarifa porcentual (tariff rate) a la que un producto cualquiera será sometido, a partir de las características del mismo. Esto es lo que hacen posible los miembros implementados por defecto en C# 8.0; básicamente, el creador de la interfaz IProduct podrá extenderla con un nuevo método GetTariffRate, siempre que suministre una implementación que se aplicará por defecto a las clases ya existentes que implementen la interfaz. Por ejemplo:

    public interface IProduct
    {
        string Name { get; }
        string Model { get; }
        string Manufacturer { get; }
        ManufacturingCountry ManufacturingCountry { get; }
        decimal BasePrice { get; }
        decimal GetSalePrice();

        // Miembro implementado por defecto
        public decimal GetTariffRate() => ManufacturingCountry switch
        {
            ManufacturingCountry.China => 0.15M,
            _ => 0,
        };
    }

Si la interfaz que contiene IProduct estuviese implementada en un ensamblado independiente, el ensamblado en el que estuviesen contenidos los electrodomésticos continuaría trabajando como antes contra la nueva versión binaria de aquél; y las aplicaciones que utilicen el ensamblado de electrodomésticos podrán llamar a GetTariffRate() aún cuando dicho ensamblado no haya sido modificado en modo alguno. En tal caso, se utilizaría la versión predefinida del método. Pero, por supuesto, el creador de IAppliance podrá cuando lo desee redefinir (override) GetTariffRate para adaptarlo a las condiciones específicas de ese tipo de productos.

Para que todo esto funcione no basta con el soporte lingüístico en C# 8.0, sino que además es imprescindible el soporte correspondiente en .NET Core 3.0 y posteriores. Puede encontrar mucha más información sobre la propuesta en la página de GitHub dedicada a la característica. Allí se la nombra también como métodos de extensión virtuales (virtual extension methods) por la similitud obvia entre la implementación de esta característica y la de los métodos virtuales; pero observe que esta nueva posibilidad no se limita exclusivamente a métodos, sino que además podrá aplicarse a propiedades, indexadores y eventos, ¡y no solo de instancia, sino también estáticos!

C# 7.x, C# 8.0: El modificador readonly en estructuras

Como comentaba en una entrada anterior, yo aún sigo descubriendo algunas de las novedades del lenguaje que fueron incluidas en las tres releases puntuales que se liberaron bajo la etiqueta 7.x. Hoy hablaremos sobre la utilización del modificador readonly al definir estructuras (structs), que como bien conoce el lector son tipos que se pasan por valor en la pila cuando se utilizan como parámetros de métodos. También veremos cómo C# 8.0 da una pequeña «vuelta de tuerca» adicional a esta característica.

La versión 7.2 de C# añadió la posibilidad de aplicar el modificador readonly a una estructura (como un todo) para indicar que el tipo en cuestión es inmutable, lo que significa que el estado de los objetos de ese tipo no puede modificarse después de su construcción. Las cadenas de caracteres (strings) son el ejemplo por excelencia de tipo de datos inmutable en .NET. Diseñar tipos inmutables aporta múltiples ventajas, entre las cuales podemos mencionar la seguridad añadida que se obtiene en aplicaciones con múltiples hilos gracias a que se hace posible evitar los problemas comunes de sincronización que se presentan cuando se lee y escribe de manera simultánea en una misma localización de memoria. Pero el uso de estructuras de datos inmutables también habilita varios escenarios en los que se mejora el rendimiento, como el que mencionábamos en la entrada anterior ya mencionada: cuando se utiliza el modificador in para pasar como parámetro una estructura inmutable, el compilador se puede dar el lujo de pasar una referencia al argumento correspondiente en lugar del valor, con la consiguiente economía de espacio en la pila y tiempo de ejecución, proporcional al tamaño de la estructura.

Un diseño inmutable de un tipo de datos para representar números complejos podría ser como el que se muestra a continuación (donde solo hemos implementado, por concisión, las operaciones de suma y magnitud). Este enfoque es, de hecho, el que se ha utilizado en la implementación de la estructura System.Numeric.Complex (presente tanto en .NET Framework como en .NET Core); solo que allí las propiedades X e Y se nombran Real e Imaginary.

public readonly struct Complex
{ 
    public double X { get; } 
    public double Y { get; } 

    public Complex(double x, double y) { X = x; Y = y; } 

    public double Magnitude => Math.Sqrt(X * X + Y * Y);
 
    public static Complex operator +(in Complex a, in Complex b) => 
        new Complex(a.X + b.X, a.Y + b.Y); 

    public override string ToString() => 
        $"({X}, {Y}), magnitude = {Magnitude}"; 
}

Pero no siempre las estructuras se diseñan para ser inmutables. Veamos el mismo ejemplo anterior, pero con un diseño no inmutable de la estructura Complex. Aquí ya se utiliza la nueva posibilidad que ofrece C# 8.0:

public struct Complex
{ 
    public double X { get; set; } 
    public double Y { get; set; } 

    public Complex(double x, double y) { X = x; Y = y; } 

    public readonly double Magnitude => Math.Sqrt(X * X + Y * Y);
 
    public override readonly string ToString() => 
        $"({X}, {Y}), magnitude = {Magnitude}"; 
}

La novedad consiste en que ahora es posible aplicar el modificador readonly a métodos individuales (incluyendo getters y setters de propiedades) de una estructura para indicarle al compilador que el método en cuestión no modifica el estado de la misma. El compilador nunca analiza si los métodos modifican o no el estado de las instancias de un tipo, tarea que podría resultar bastante compleja en el caso general. Así que queda de nuestra parte indicárselo. Con ello, además de la ganancia expresiva, podríamos obtener mejoras en el rendimiento en aquellos casos en que el compilador se pudiera ver obligado a crear lo que se conoce como una copia defensiva (defensive copy) de la estructura antes de hacer una llamada a método. Como ejemplo, elimine el modificador readonly de la propiedad Magnitude (pero no el del método ToString) en el código anterior. Al compilar, obtendrá la siguiente advertencia para ToString:

CS8656: Call to non-readonly member ‘Complex.Magnitude.get’ from a ‘readonly’ member results in an implicit copy of ‘this’.

Para asegurar que ToString sea readonly, el compilador, como no tiene constancia explícita de que Magnitude no modifique la estructura, deberá hacer una copia de this antes de pasarla a la llamada de modo que, en caso de que esa modificación ocurra, se produzca sobre la copia y no sobre la estructura original. Si usted descompila el código a IL, podrá ver claramente tales copias, que se implementan utilizando la instrucción ldobj. Observe que esta advertencia no aplica a los getters de propiedades implementadas automáticamente; si no, habrían tres advertencias en lugar de una. El compilador sí sabe a ciencia cierta que esos getters son inofensivos :-).

Sin duda alguna, la posibilidad de aplicar readonly a los métodos de una estructura es otra pequeña adición positiva al lenguaje, que podría llegar a ser bastante útil en escenarios de alto rendimiento.


Si el lector quiere entretenerse un poco más lidiando con las estructuras, aquí le dejo otra que he creado apoyándome en Complex: una estructura que aglutina los parámetros y las raíces de una ecuación de segundo grado. Para mí siempre es un ejercicio útil recordar fundamentos matemáticos que hace tiempo no utilizo. Todo es válido a la hora de luchar contra el tirano Alzheimer :-).

Por supuesto, se acepta todo tipo de críticas y opiniones.

public struct QuadraticEquation
{
   private double _a, _b, _c;
   private Complex _root1, _root2;

   public QuadraticEquation(double a, double b, double c)
   {
      _a = a; _b = b; _c = c;
      Solve(_a, _b, _c, out _root1, out _root2);
   }

   public double A { get { return _a; } set { _a = value; Solve(); } }
   public double B { get { return _b; } set { _b = value; Solve(); } }
   public double C { get { return _c; } set { _c = value; Solve(); } }

   public Complex Root1 { get { return _root1; } }
   public Complex Root2 { get { return _root2; } }

   private void Solve()
   {
      Solve(_a, _b, _c, out _root1, out _root2);
   }
   private static void Solve(double a, double b, double c, 
      out Complex root1, out Complex root2)
   {
      double d = b * b - 4.0 * a * c;
      if (d >= 0)
      {
         root1 = new Complex((-b + Math.Sqrt(d)) / (2.0 * a), 0);
         root2 = new Complex((-b - Math.Sqrt(d)) / (2.0 * a), 0);
      }
      else
      {
         root1 = new Complex(-b / (2.0 * a), Math.Sqrt(-d) / (2.0 * a));
         root2 = new Complex(-b / (2.0 * a), Math.Sqrt(-d) / (2.0 * a));
      }
   }
}

class Program
{
   static void Main(string[] args)
   {
      // x^2 + 2x + 2, with roots (1, 1) and (1, -1)
      var equation = new QuadraticEquation(1, 2, 2);
      Console.WriteLine($"Root1 = {equation.Root1}");
      Console.WriteLine($"Root2 = {equation.Root2}");
   }
}