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!

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}");
   }
}

C# 8.0: Funciones locales estáticas

En la entrada anterior comenzamos a hablar sobre las nuevas características que se añadirán a C# 8.0, partiendo de las más simples, en aquella ocasión las declaraciones using (using declarations). Hoy nos centraremos en otra al parecer muy sencilla, las funciones locales estáticas (static local functions). En este caso, la impresión que tengo es que el equipo de diseño del lenguaje no hizo caso a la cita de Einstein que mencionamos la vez anterior y se quedó un poco “corto” en la oferta; o tal vez será que habrán decidido dejar algo para la futura versión 9 :-).

En el artículo que dedicamos a las funciones locales hace ya más de año y medio mencionábamos las ventajas de la anidación de funciones. Para mí (tal vez por provenir del mundo de Pascal, donde tal posibilidad siempre existió), la principal radica en que permiten acotar el ámbito de utilización del identificador correspondiente, y con ello promueven un mayor encapsulamiento del código. Todo lo demás es, simplemente, secundario.

Al hacer estática una función local en C# 8.0, el programador le estaría diciendo al compilador que dicha función no depende en lo absoluto del contexto que la contiene; o sea, que se trata de una función “pura” que se comunica con dicho contexto exclusivamente a través de los parámetros de entrada y el valor de retorno. Un ejemplo sencillo podría ser el siguiente, donde se calcula la raíz cuadrada de un número utilizando el método de aproximaciones sucesivas de Newton:

static double SquareRoot(double x, double epsilon)
{
    Debug.Assert(x >= 0);
    Debug.Assert(epsilon > 0 && epsilon < 0.1);

    double y = 1.0;
    while (true)
    {
        double y1 = Average(y, x / y);
        if (Math.Abs(y1 - y) < epsilon)
            break;
        y = y1;
    }
    return y;

    static double Average(double a, double b) => 0.5 * (a + b);
}

La restricción de no acceder a los elementos del contexto que las rodea limita hasta cierto punto las posibles aplicaciones de este tipo de funciones, en particular a la hora de capturar las variables locales del método que las contiene. Por ejemplo, la función interna que utilizamos en el artículo anterior no podría ser marcada como estática. No obstante, la adición de esta nueva posibilidad es sin duda positiva, en particular porque permite al programador indicar al compilador de manera explícita sus intenciones, y que éste verifique que realmente la función no se excede de los límites que se han establecido para ella. De paso, se hacen posibles ciertas pequeñas mejoras en el rendimiento, al no ser necesario gestionar el mecanismo de capturas, como puede leerse aquí.

Cuando mencionaba al principio que pensaba que la oferta se había quedado corta, estaba pensando específicamente en otra característica que creo que complementaría a ésta en gran medida: las variables locales estáticas. Como bien conocen aquellos que utilizan C, C++ o incluso Visual Basic, una variable local estática es una variable local que conserva su valor de una llamada a la función a otra; o sea, que es local en lo que a ámbito se refiere, pero global por su tiempo de vida. Las razones por las cuales esta característica nunca ha sido añadida C# (por ejemplo, las que se enumeran aquí) nunca me convencieron del todo. De nuevo, lo principal para mí es la acotación al máximo del ámbito; no me vale el argumento de que “eso se puede lograr añadiendo un campo estático a la clase”.

El ejemplo de uso por excelencia de las variables locales estáticas para mí siempre será un generador de números pseudo-aleatorios. En tales funciones, cada nuevo valor se computa utilizando una fórmula compleja (que muy frecuentemente involucra números primos) a partir del valor anterior; o sea, que la variable que almacena el último valor generado deberá conservar su valor para la próxima llamada, en la que servirá como argumento. Existen múltiples algoritmos de generación de tales secuencias; aquí utilizaremos el Algoritmo Linear Congruente que describió James McCaffrey en su artículo “Lightweight Random Number Generation” de MSDN Magazine (agosto de 2016).

La clase que allí se presenta para implementar el generador es la siguiente:

public class LinearConRng
{
  private const long a = 25214903917;
  private const long c = 11;
  private long seed;
  public LinearConRng(long seed)
  {
    if (seed < 0)
      throw new Exception("Bad seed");
    this.seed = seed;
  }
  private int next(int bits) // helper
  {
    seed = (seed * a + c) & ((1L << 48) - 1);
    return (int)(seed >> (48 - bits));
  }
  public double Next()
  {
    return (((long)next(26) << 27) + next(27)) / (double)(1L << 53);
  }
}

En el código anterior, la variable seed (“semilla”), que es la que se actualiza en cada iteración, ha sido promovida a variable de instancia con el ¿aceptable? pretexto de poder ser inicializada desde fuera a través del parámetro del constructor. Lo cierto es que en la práctica, en este tipo de algoritmos la semilla se calcula utilizando, por ejemplo, la hora actual del reloj del ordenador, y ese cálculo se podría hacer perfectamente como parte de la inicialización de una variable local estática (en caso de que existieran). No faltará aquí quien ponga el grito en el cielo pensando en que esta inicialización podría complicarse en contextos complejos, de múltiples hilos, etc. Mi respuesta: nada con lo que un compilador inteligente apoyado en el runtime de .NET no pueda lidiar con éxito.

Si existieran las variables locales estáticas, la clase anterior podría modificarse como sigue:

public static class LinearConRng
{
  // *** WARNING: not valid C# 8.0 ***
  public static double Next()
  {
    private static long seed = DateTime.Now.Ticks; // !!!

    private static int next(int bits) // helper
    {
      const long a = 25214903917;
      const long c = 11;

      seed = (seed * a + c) & ((1L << 48) - 1);
      return (int)(seed >> (48 - bits));
    }

    return (((long)next(26) << 27) + next(27)) / (double)(1L << 53);
  }
}

Un detalle que me llamó la atención mientras estaba jugando con el código es que incluso referencias externas a constantes como a y c desde dentro de una función local estática producen el error CS8421: A static local function cannot contain a reference to ‘a’. Eso parece indicar que estas entidades se tratan como variables locales (a las que el compilador no permite hacer asignaciones después de su inicialización) más que como verdaderas constantes. Tengo que comprobar qué dice la especificación de C# al respecto.

C# 8.0: Declaraciones using

“Make everything as simple as possible, but not simpler”
Albert Einstein

Finalmente he logrado sacar un rato libre para empezar a probar la preview de C# 8.0 (versión que vendrá atada a .NET Core 3.0, como se preveía) y evaluar de manera práctica las nuevas incorporaciones que presenta. Y para empezar por las novedades más sencillas, este artículo se centra en las declaraciones using (using declarations), que simplifican el código y facilitan su lectura al mismo tiempo que nos ahorran unos cuantos paréntesis y llaves.

En esencia, un código como el siguiente:

{
    // ...
    using (var file = new System.IO.StreamWriter(filePath))
    {
        foreach (string line in lines)
            file.WriteLine(line);
    } // aquí se dispone de 'file'
    // *** otras sentencias
}

ahora podrá simplificarse de esta manera:

{
    // ...
    using var file = new System.IO.StreamWriter(filePath);
    foreach (string line in lines)
        file.WriteLine(line);
    // *** otras sentencias
} // aquí se dispone de 'file' 

Tenga en cuenta que al usar la nueva sintaxis, la llamada automática a Dispose() para disponer del objeto referenciado por la variable definida (en este caso file) se produce al final del bloque (ámbito) de su declaración, por lo que en el fragmento anterior el objeto seguiría “vivo” durante la ejecución de “otras sentencias“, a diferencia de lo que ocurriría si se utilizara la sintaxis mostrada inicialmente. Pero probablemente el escenario más común sea aquél en que “otras sentencias” es un conjunto vacío (imagine, por ejemplo, que el bloque anterior fuera el cuerpo de un método que escribe un conjunto de líneas de texto a un fichero). Pienso que, en el caso general, la ganancia en legibilidad que produce la indentación simplificada es razón más que suficiente para dar preferencia a esta nueva sintaxis.

Revisando los artículos que se han escrito ya sobre las declaraciones using, encontré varios muy recomendables, en particular éste de @vcsjones, del que extraje este ejemplo simpático:

public static bool MightBeExe(string filePath)
{
    using var file = File.Open(filePath, FileMode.Open);
    var firstBytes = new byte[2];
    var bytesRead = file.Read(firstBytes, 0, 2);
    return bytesRead == 2 && 
        firstBytes[0] == (byte)'M' && firstBytes[1] == (byte)'Z';
}

Este método determina si un fichero específico pudiera ser un ejecutable (aunque no llevara la extensión .EXE), basándose en algo que es como un monumento a la vanidad humana: el formato de los ficheros ejecutables de DOS y Windows comienza con “MZ” porque así lo quiso el arquitecto de Microsoft que los diseñó, Mark Zbikowski.

El hecho de que en este método detrás de “using var” aparezcan dos declaraciones var me hizo pensar en lo siguiente: ¿y no será que using es realmente innecesario, y que podríamos dejar a un compilador inteligente la tarea de determinar si cualquier objeto instanciado localmente implementa IDisposable, para disponer de él automáticamente al final del bloque? Parece que los miembros del equipo de C# pensaron también en eso, y decidieron no llegar a tales extremos por las complicaciones potenciales en relación con la propiedad (ownership) de los objetos. Simplificar, sí, pero no más de lo necesario – es lo que parecen haber pensado. Este hilo de reddit contiene bastante información al respecto.


Referencia cultural: Sin duda uno de los cerebros más brillantes que ha dado la humanidad, Albert Einstein no solo nos dejó su Teoría de la Relatividad, sino también una buena cantidad de citas interesantes.

El modificador in para parámetros de entrada

“… Someone’s knocking at the door
Somebody’s ringing the bell
Do me a favor, open the door, and let ‘em in…”
Paul McCartney and Wings, “Let ‘Em In” (1976)

Aunque ya todos estamos esperando la presentación oficial de Visual Studio 2019 y C# 8.0 el próximo martes, 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. En particular, hoy quiero referirme al modificador in, que ahora se puede asociar a los parámetros de un método de manera similar a como se utilizan ref y out en C# desde el principio de los tiempos.

Básicamente, cuando un parámetro se declara in, el compilador impedirá que dicho parámetro pueda modificarse dentro del cuerpo del método. Se trata de un paso pequeño, pero importante, en la dirección de la especificación declarativa de las propiedades de los métodos, principalmente útil en el caso de los parámetros que son clases (y por tanto se pasan por referencia) y podrían modificarse de manera inadvertida. Por otra parte, el hecho de que el parámetro no va a ser modificado hace en principio irrelevante si éste es pasado por valor o por referencia; esto, a su vez, hace posible la generación del código más eficiente posible en el lugar de las llamadas. Éste es el escenario de mayor relevancia en relación con el rendimiento, específicamente en el caso de los parámetros de tipo struct (que son tipos-valor) cuyo tamaño en memoria exceda el tamaño de la palabra del ordenador. En tales casos, el compilador generará código para pasar el parámetro por referencia y no por valor, con el consiguiente ahorro en la copia de datos en la pila. Un ejemplo podría ser el siguiente:

        // Computes the distance from a to b.
        public static double GetDistance(
            in System.Drawing.Point a, 
            in System.Drawing.Point b) 
        { 
            int dx = a.X - b.X; 
            int dy = a.Y - b.Y; 
            return System.Math.Sqrt(dx*dx + dy*dy); 
        }

Este ejemplo me trae a la mente una demo gráfica que solía hacer Microsoft por allá por el año 2000 cuando .NET estaba naciendo. La idea subyacente era que en .NET System.Drawing.Point es una estructura, mientras que en Java (el rival a batir por aquella época) el tipo correspondiente es (o era, no sé si la cosa habrá cambiado con el tiempo) una clase; un algoritmo que tenga que generar dinámicamente muchos puntos será bastante más lento en el segundo caso, debido a la necesidad de gestionar la creación en el heap de numerosos objetos. El uso del nuevo modificador in de C# 7.2 habría contribuido en aquel momento a hacer aún mayor la ventaja.

Personalmente, la inclusión del modificador in en el lenguaje me agrada mucho porque de alguna manera completa la transición de C# de un lenguaje más primitivo en el que los parámetros se denotan pensando en cómo los argumentos correspondientes serán pasados en tiempo de ejecución (por valor o por referencia), como ocurría en lenguajes históricos como C o Pascal, en un lenguaje más moderno y declarativo en el que los parámetros se denotan pensando en su propósito (de entrada, salida o entrada/salida), dejando al compilador los detalles de cómo implementar las llamadas de la manera más eficiente. Ada fue probablemente el lenguaje en el que esta idea se presentó claramente por primera vez.

El uso del modificador in se me antoja imprescindible cuando se esté creando una nueva aplicación o librería; como ocurre con casi todas las características que han sido incluidas después de la concepción original del lenguaje (lo que en inglés se diría un afterthought), debe utilizarse con cierta precaución al incorporarla en proyectos existentes; consulte los documentos de Microsoft al respecto (como éste) para más información.


Referencia musical: “Let ‘Em In” fue el segundo single del quinto disco de Paul McCartney con la banda Wings (“Wings at the Speed of Sound“, 1976). Sonó bastante por la radio, aunque no tanto como “Silly Love Songs“, que fue el número 1 de la lista Billboard de ese año.

De Span a Memory

“…Memoria, memoria
(no, I don’t have a gun)…”
Nirvana, “Come as You Are” (1992)

En nuestra entrada anterior presentamos la clase genérica Span<T>, que fue introducida oficialmente con C# 7.2, seguramente por el hecho de aprovecha algunas novedades relativamente menores incorporadas por primera vez al lenguaje en las distintas actualizaciones de la versión 7 (en particular, los valores de retorno por referencia – ref returns, de los que hemos hablado aquí, y las nuevas posibilidades que ofrece stackalloc, que pueden consultarse en esta página de JetBrains). Span<T> y su homólogo de solo lectura ReadOnlySpan<T> nos permiten hacer referencia de una manera uniforme a cualquier zona de memoria contigua de datos homogéneos, independientemente de dónde esa memoria está situada y de cómo ha sido obtenida.

La vez anterior no mencionamos que Span<T> tiene un pequeño inconveniente: a causa de su implementación (el lector interesado encontrará información detallada en los documentos a los que ya hemos hecho referencia), las instancias de Span<T>, si bien pueden apuntar a cualquier zona arbitraria de memoria, ellas mismas sólo pueden residir en la pila (stack) en forma de variables locales o parámetros de entrada de funciones (siempre que éstas no sean asíncronas, como veremos más adelante). Por ejemplo, si se trata de compilar la siguiente clase:

using System;
public class ThisWontCompile
{
    private Span<int> _span;
}

se obtendrá el error “Field or auto-implemented property cannot be of type ‘Span<int>’ unless it is an instance member of a ref struct (CS 8345)“.

Aquí es donde entra a jugar su papel la clase Memory<T>, cuyas instancias sí pueden residir en el heap. Memory<T> no implementa una API tan amplia como Span<T>, pero sí ofrece una propiedad Span que devuelve un Span<T> asociado a la misma zona de memoria.

Un caso que podría presentársele con cierta frecuencia y en el que deberá cambiar Span<T> por Memory<T> es al crear métodos asíncronos que reciban un span. Recuerde que al “desenrollar” un método asíncrono el compilador genera una clase para capturar el estado local y los parámetros de entrada (el mecanismo se describe muy bien aquí). En el caso que nos ocupa, la clase generada tendría que tener un campo de tipo Span<T>, que como hemos visto antes es imposible.

Suponga que quisiéramos hacer asíncrono el método QuickSort de nuestra entrada anterior. Con tan solo añadir al método el modificador async obtendríamos el error “Parameters or locals of type ‘Span<int>’ cannot be declared in async methods or lambda expressions (CS 4012)“:

  public static async void QuickSort<T>(Span<T> span) where T: IComparable

Podríamos resolver el problema así:

  public static async void QuickSort<T>(Memory<T> memory) where T: IComparable 
  {
      int i = 0, j = memory.Length - 1;
      T pivot = memory.Span[(memory.Length - 1) / 2];

      while (i <= j)
      {
          while (memory.Span[i].CompareTo(pivot) < 0) i++;
          while (memory.Span[j].CompareTo(pivot) > 0) j--;
          if (i <= j)
          {
              T tmp = memory.Span[i];
              memory.Span[i] = memory.Span[j];
              memory.Span[j] = tmp; 

              i++; j--;
          }
      }

      // Llamadas recursivas
      if (j > 0)
          await QuickSort<T>(memory.Slice(0, j + 1));
      if (i < memory.Length - 1)
          await QuickSort<T>(memory.Slice(i, memory.Length - i));
  }

Este es un caso relativamente bastante sencillo, pero en general (como hemos dicho ya alguna vez aquí) siempre que se trabaja directamente con la memoria se debe ser sumamente cuidadoso. Si piensa utilizar estas clases en contextos más avanzados, asegúrese de leer antes las recomendaciones oficiales de Microsoft sobre el uso de Memory<T> y Span<T>.


Referencia musical: A principios de los ’90 dejé de oír rock (tuve que concentrarme seriamente en buscarme la vida ), así que no me enteré de quién era Nirvana hasta más de una década después, cuando hice mi primer viaje a Seattle y Microsoft nos llevó a visitar el excelente Museum of Pop Culture, que exhibe numerosos objetos memorables relacionados con el grupo. “Come as You Are” fue el segundo single del disco “Nevermind“, que los lanzó a la fama. Puedo dar fe de que el tema se sigue escuchando aún con cierta frecuencia en las emisoras de radio de rock clásico.

Generalización del acceso a memoria con Span

“…as the links span our endless caresses
For the freedom of life everlasting…”
Yes, “The Revealing Science of God” (1973)

¡Feliz 2019! Deseo de corazón a todos mis lectores que hayan tenido una buena despedida del año que recién terminó. “Año nuevo, vida nueva”, decía mi abuela Guillermina, y uno de mis propósitos para este año es recuperar la sistematicidad en la escritura, que perdí completamente a finales del año pasado.

Esta primera entrada del año, que llevaba en el tintero unos cuantos meses sin que yo hubiera logrado terminarla, trata sobre el tipo de datos Span<T> (espacio de nombres System.Memory), de relativamente reciente aparición: fue incorporado a .NET Core 2.1. Para utilizarlo en proyectos de .NET Framework, sin embargo, tendrá que importar de Nuget el paquete System.Memory, como puede verse en la captura de pantalla a continuación. Como es muy pronto para empezar a quejarse ya, dejaré las quejas al respecto para el final.

Span<T> es un nuevo tipo por valor (value type) que nos permitirá hacer referencia de una manera uniforme a cualquier zona de memoria contigua de datos homogéneos, independientemente de si esta zona de memoria pertenece a un objeto de código manejado tradicional residente en el heap (T[]), a un buffer nativo (T*) o array creado en la pila mediante stackalloc, o incluso una estructura de datos a la que hayamos obtenido una referencia IntPtr mediante la interacción con código no manejado. A partir de cualquiera de esas fuentes se puede crear una variable de tipo Span<T> (o su pariente de solo lectura ReadOnlySpan<T>). Aparte del hecho de que Span<T> garantiza un acceso con validación de rango a cualquiera de los elementos de la estructura a través de un índice, como si de un array se tratara (s[i]), este tipo de datos ofrece otra ventaja que es muy importante de cara al rendimiento: no reserva buffers intermedios y evita las copias de datos innecesarias. Deja vu! Empezamos el nuevo año como mismo terminamos el viejo: hablando de un tipo de datos por valor (estructura) que ofrece mejoras de rendimiento.

El siguiente ejemplo muestra cómo crear un Span<T> a partir de un array, cómo hacer referencia a un elemento cualquiera, y (lo mejor de todo), cómo utilizar el método Slice para denotar a un subconjunto (“rebanada”) de los elementos del span. Para finalizar, ordeno la rebanada mediante una implementación “espantosa”1 de QuickSort. Tengo la impresión de que este algoritmo se logra expresar mucho más elegantemente cuando se utiliza Span<T>.

using System;

namespace SpanDemo
{
    public static class SortingRoutines
    {
        static void Main(string[] args)
        {
            var random = new Random();

            int[] values = new int[100]; 
            for (int i = 0; i < 100; i++)
                values[i] = random.Next(0, 1000);

            var span = new Span<int>(values);
            // Referencias a elemento
            Console.WriteLine("First element: {0}", span[0]);
            span[1]++;
            // "Rebanada" del span original
            var span2 = span.Slice(start: 3, length: 10);

            QuickSort(span2);
            foreach (int i in span2)
                Console.WriteLine(i);
        }

        public static void QuickSort<T>(Span<T> span) where T: IComparable 
        {
            int i = 0, j = span.Length - 1;
            T pivot = span[(span.Length - 1) / 2];

            while (i <= j)
            {
                while (span[i].CompareTo(pivot) < 0) i++;
                while (span[j].CompareTo(pivot) > 0) j--;
                if (i <= j)
                {
                    T tmp = span[i];
                    span[i] = span[j];
                    span[j] = tmp; 

                    i++; j--;
                }
            }

            // Llamadas recursivas
            if (j > 0)
                QuickSort<T>(span.Slice(0, j + 1));
            if (i < span.Length - 1)
                QuickSort<T>(span.Slice(i, span.Length - i));
        }
    }
}

Dejo al lector la investigación de cómo se implementa internamente la estructura Span<T>, algo que describen perfectamente tanto Ahson Khan en Code Magazine como Stephen Toub en MSDN Magazine.

Para finalizar, la diatriba prometida contra la exclusión de Span<T> de .NET Framework. No es la necesidad de añadir un paquete, ni mucho menos; es la fractura que empieza a producirse en el lenguaje por culpa de ello. Por ejemplo, Microsoft señala en este artículo que los futuros índices y rangos de C# 8.0 dependen de Span<T> y otras nuevas características, y no estarán disponibles al desarrollar aplicaciones para .NET Framework. Oficialmente, nos dicen que estos nuevos tipos no se añaden a .NET Framework para no comprometer la estabilidad de la plataforma; en el fondo, lo que pienso es que lo que Microsoft quiere es obligarnos a ir hacia .NET Core. Tal vez algún lector logre aclararme esta confusión.


1 “Espantoso” no viene de espanto, sino de Span<T> .

Referencia musical: El grupo británico Yes (elegido al Salón de la Fama del Rock’n’Roll en 2017) es uno de los máximos exponentes de la corriente que, por razones obvias, se ha dado en llamar rock sinfónico o progresivo, y “Tales from Topographic Oceans” (1973), al que pertenece la referencia musical de hoy, uno de los primeros exponentes de álbum conceptual, en este caso inspirado en la “Autobiografía de un Yogi”, de Paramahansa Yogananda.

La estructura ValueTask

“… ‘Cause I’ve been here, and I’ve been there,
Seems like I’ve been everywhere before.
I’ve seen it all a hundred times
Still I think there surely must be more…”
Kansas, “Paradox” (1977)

Repasando la enorme cantidad de características añadidas a  C# en las versiones 6 y 7.x, realmente puede dar la impresión de que (como dice la referencia musical de hoy) no queda nada más que inventar. Sin embargo, ya tenemos casi a la vuelta de la esquina a C# 8.0, con una buena cantidad de novedades relevantes.

Buscando entre las características incorporadas a partir de la versión 7.0 de las que no he hablado aún aquí (y que no sean totalmente triviales, como por ejemplo las mejoras en la representación de constantes numéricas), encontré la generalización de los tipos de retorno de los métodos asíncronos (generalized async return types), una característica que ha cobrado importancia principalmente debido a la aparición de un nuevo tipo que se ha hecho posible gracias a ella, ValueTask<T>,  que hace posible mejorar el rendimiento en ciertos escenarios de utilización de llamadas asíncronas.

Como seguramente sabrá, antes de C# 7.0 los métodos asíncronos solo podían devolver los tipos Task<TResult> (si se retorna un valor), Task (si el método realiza una acción y no retorna ningún valor) o void (en el caso de los gestores de eventos asíncronos1). A partir de  C# 7.0, se permite también que un método asíncrono devuelva un objeto de cualquier tipo que ofrezca un método GetAwaiter que a su vez retorne un objeto que implemente la interfaz ICriticalNotifyCompletion (espacio System.Runtime.CompilerServices). Lo más apropiado para esta entrada sería crear precisamente un tipo así, pero tengo la impresión de que eso tendría un interés puramente académico: no he encontrado en la web ninguna referencia a un tipo similar creado por terceros. Da la impresión de que esta generalización se ha hecho con el objetivo específico de abrir las puertas a la definición de ValueTask<T>, que precisamente cumple con ese requisito y que además tiene otra característica esencial: no es un tipo por referencia (class) sino un tipo por valor (struct). Es este hecho el que hace posible una mejora del rendimiento en ciertos escenarios de uso de los métodos asíncronos, como describiremos a continuación.

Para el ejemplo de hoy utilizaré la peor manera posible (o la más ingenua, si queremos ser más políticamente correctos) de implementar el cálculo del n-simo elemento de la serie de Fibonacci, en la que cada elemento subsiguiente de la serie se obtiene sumando los dos anteriores a él. Y como siempre es posible empeorar aún más algo que ya está mal, he añadido una “guinda al pastel” haciendo la que las llamadas recursivas se realicen de manera asíncrona. Esta variante asíncrona es la que me servirá como base para mostrar cómo el uso de ValueTask mejora el rendimiento (¿le parece a usted, querido lector, que el fin siempre justifique los medios? ;-)). Eche un vistazo al siguiente código:

using System;
using System.Threading.Tasks;

namespace Fibo
{
    class MainClass
    {
        static void Main(string[] args)
        {
            TimeMethod(() => { Console.WriteLine(Fibo(40)); } );
            TimeMethod(() => { Console.WriteLine(FiboAsync(40).Result ); } );
        }

        static long Fibo(int n)
        {
            if (n <= 1)
                return 1;

            return Fibo(n - 2) + Fibo(n - 1);
        }

        static async Task<long> FiboAsync(int n)
        {
            if (n <= 1)
                return 1;

            return await FiboAsync(n - 2) + await FiboAsync(n - 1);
        }

        static void TimeMethod(Action action)
        {
            DateTime t1 = DateTime.Now;
            action();
            DateTime t2 = DateTime.Now;
            Console.WriteLine("Ellapsed " +
                (t2 - t1).TotalMilliseconds + " ms.");
        }
    }
}

La salida que produce el programa anterior es la siguiente:

165580141
Ellapsed 1612.459 ms.
165580141
Ellapsed 44734.77 ms.

Así que introduciendo la asincronía sin que hubiera necesidad alguna hemos hecho el código unas 27 veces más lento :-). Pero esta entrada no intenta predicar sobre en qué situaciones se deben utilizar los métodos asíncronos; este enlace podría ser un buen comienzo para ello. Aquí yo solo intentaba buscar un ejemplo en el que el uso de ValueTask<T> ofreciera una mejora en rendimiento sobre Task<T>, y me pareció que éste podría ser un buen caso, porque en él se realiza una gran cantidad de cálculos repetidos, y cada invocación recursiva termina provocando llamadas a los casos base, en los que el resultado (1) está predeterminado.

Para poder hacer uso de la estructura ValueTask<T>, hace falta importar de Nuget el paquete System.Threading.Tasks.Extensions:

Ahora, sustituyendo la única referencia a Task en el código fuente por ValueTask y ejecutando, se obtiene como promedio lo siguiente:

165580141
Ellapsed 1617.608 ms.
165580141
Ellapsed 39123.887 ms.

La ganancia no es tanta como yo esperaba, pero en cualquier caso un 13% de mejora en el rendimiento no es nada despreciable.

La razón por la que el rendimiento es superior al utilizar ValueTask estriba en que cuando se utiliza el tipo Task, que es una clase, es necesario cumplir con el protocolo de instanciación de un objeto incluso en los casos base, para los que el resultado está predeterminado de antemano y en principio no haría falta ninguna tarea (retornando la llamada de forma síncrona). Al ser ValueTask un tipo por valor, la tarea a devolver se aloja en la pila, evitando así la costosa instanciación en todos esos casos base.

Note que también el uso de ValueTask hace posible una menor presión sobre la memoria (memory pressure), y una disminución en la frecuencia entre ejecuciones del recolector de basura (garbage collector). Este vídeo de Andrea Angella ilustra fenomenalmente este beneficio.


1 Frecuentemente encontrará recomendaciones sobre no crear métodos async void, como ésta de Bill Wagner.

Referencia musical: “Paradox” pertenece al “Point of Know Return” (1977), que es mi disco favorito de mi banda favorita, Kansas, a pesar de que la critica generalmente considera superior al proyecto anterior, “Leftoverture” (1976) – vea por ejemplo este reportaje de Rolling Stone que lo coloca en el lugar 32 de los mejores álbumes de rock progresivo de todos los tiempos.

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.