Novedades de c# 9

Este año 2020 está siendo como una montaña rusa. Y el mundo de las tecnologías de desarrollo de Microsoft no iba a ser diferente. En noviembre se espera la presentación de .Net 5 - A unified platform. No, no es el título de una película de sábado por la tarde de antena 3. Es la nueva plataforma para gobernarlas a todas. Una plataforma para encontrarlas, una plataforma para atraerlas a todas y atarlas en las tinieblas.

.Net 2019 vs. 2020

Pero eso de que junten todos los frameworks en uno, simplifiquen su nombre a .Net y le den una versión con rima graciosa, no es lo que importa en realidad. Lo que más nos excita es que a .Net 5 le acompaña una nueva versión de su lenguaje insignia. Hoy os presentamos las novedades de C# 9 y sus notas según la escala sexi-loca.

escala sexy loca

Las normas son las de siempre: eje vertical es lo útil que nos resulta y el eje horizontal la locura de su implementación. Y el objetivo de cada característica es estar por encima de la diagonal Vicky Mendoza (x=y) para poder considerarse una buena característica.

Y antes de que empecéis a juzgar las notas de cada una de las features, comentar que el proceso de asignación de este año ha sido rigurosamente objetivo: hemos desempolvado los dados de El Señor de los Anillos, el juego de rol de la Tierra Media y los hemos lanzado 3 veces cada uno. Después hemos calculado la mediana. Y para terminar se le ha aplicado un factor de corrección delta. A prueba de todo error.

¡Al turrón!

Como cada año, añadir que algunas de estas funcionalidades no llegarán a aparecer y otras podrían llegar a publicarse, pero de una forma diferente a la que aquí exponemos.

Init-only properties

Cuántas veces habremos tenido un error de compilación por meter un objeto como readonly en el constructor y quererlo asignar en otro momento por primera y única vez. La verdad es que no muchas, pero si algunas. Y esto ya justifica esta funcionalidad.

Cojamos como ejemplo este código:

public class Place
{
  public string Name { get; set; }
  public string Founder { get; set; }
}

var p = new Place
{
  Name = "Mordor",
  Founder = "Sauron"
};

p.Name = "Moria";

Ahora cambiemos el set de las propiedades por un init y tendremos un objeto inmutable:

public class Place
{
  public string Name { get; init; }
  public string Founder { get; init; }
}

var p = new Place
{
  Name = "Mordor",
  Founder = "Sauron"
};

p.Name = "Moria"; // Error

Es genial la inmutabilidad, ¿verdad?

Y si os estáis preguntando como se lleva el init con las propiedades readonly, os diré que la mar de bien:

public class Place
{
  private readonly string _name;
  private readonly string _founder;

  public string Name
  {
    get => _name;
    init => _name = (value ?? throw new ArgumentNullException(nameof(Name)));
  }

  public string Founder
  {
    get => _founder;
    init => _founder = (value ?? throw new ArgumentNullException(nameof(Founder)));
  }
}

Esta funcionalidad puede parecer trivial. Pero abrir la opción a objetos inmutables de una forma sencilla y evitar ciertos errores con los readonly está muy bien.

Valoración:

Useful = 7

Crazy = 2

Records

Un record es un nuevo tipo especial, cuyo origen podemos encontrar en el lenguaje F#, que está basado en una class de C# y hereda sus características.

public record Place
{
  public string Name { get; set; }
  public string Founder { get; set; }
}

Podríamos crear un record inmutable, con su constructor y su método de deconstrucción:

public record Place
{
  public string Name { get; init; }
  public string Founder { get; init; }

  public Place(string name, string founder)
    => (Name, Founder) = (name, founder);

  public void Deconstruct(out string name, out int founder)
    => (name, founder) = (Name, Founder);
}

O definirlo como un record de tipo posicional:

public record Place(string Name, string Founder);

Esto dos últimos ejemplos nos permitirán realizar operaciones de construcción y deconstrucción:

var place = new Place("Mordor", "Sauron");
var (name, founder) = place;

Además, los tipos record admiten herencia:

public record NamedRecord(string Name);
public record Place(string Name, string Founder) : NamedRecord(Name);

Y añaden nuevos usos a la palabra clave with:

var place = new Place("Mordor", "Sauron");
var otherPlace = place with { Name = "Barad-dûr" };

Aquí se crearía un nuevo record como una copia de place y se cambiaría la propiedad Name. Una sintaxis muy cómoda que seguro que nos trae más opciones en el futuro.

Si queréis profundizar más en esta feature os recomiendo una lectura de un artículo sobre records que escribió Eduard Tomàs en su blog.

De cualquier forma, consideramos que esto de los records es la funcionalidad estrella de esta versión 9 de C#. Y creemos que va a ser tan útil como loco puede resultar el hacer tanta magia por detrás usando una palabra clave nueva.

Valoración:

Useful = 9

Crazy = 8

Pattern matching

En la versión 8 de C# ya pudimos ver un gran avance en el Pattern matching. En esta nueva entrega sigue mejorando.

Lo que podríamos definir con las características de C# 8 como:

string SayPopulation(object place) =>
  place switch
  {
    Place t when t.Population > 1000000 => "Great orc horde",
    Place t when t.Population <= 1000000 && t.Population >= 100000 => "Orc horde",
    Place t when t.Population < 100000 && t.Population >= 100 => "A few orcs",
    Place _ => "Orcs",

    _ => throw new ArgumentException("Not a known place type", nameof(place))
  };

Ahora queda simplificado por el uso de nuevas palabras clave como and y or:

string SayPopulation(object place) =>
  place switch
  {
    Place t => t.Population switch
    {
      > 1000000        => "Great orc horde",
      <= 1000000 and >= 100000 => "Orc horde",
      < 100000   and >= 100  => "A few orcs",
                   _ => "Orcs"
    },
    not null => throw new ArgumentException("Not a known place type", nameof(place)),
    null   => throw new ArgumentNullException(nameof(place))
  };

Además, se ha añadido la comparación a null y la not null. Esta última nos vendrá muy bien la hora de realizar comparaciones de tipo:

if (!(e is Customer)) { ... } // old
if (e is not Customer) { ... } // C# 9

Una evolución que viene siguiendo la línea de simplificar la sintaxis y que ayudará a tener un código más claro.

Valoración:

Useful = 8

Crazy = 3

Target typing

Si ya tenemos el tipo, no tendremos que volver a repetirlo cuando hagamos un new:

Place p = new ("Mordor", "Sauron");

Y usando ternarios tipo ? o ?? ahora ya no tendremos que hacer casting de objetos con una base comun o del null:

Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type

Esta última opción me parece que ayudará muchísimo, porque muchas veces terminas haciendo un casting en un ternario, que lo ensucia bastante y hace que pierda claridad. La mayor parte de las veces que lo hago, termino metiendo un bloque if para que no quede una línea tipo churro. Pero con esta nueva feature quedará perfecto.

Valoración:

Useful = 7

Crazy = 1

Covariant returns

La contravarianza habilita la conversión de referencias implícita de tipos base. Esto implica que podamos crear una clase con un método y otra derivaba con la sobreescritura de ese método, pero usando un objeto de retorno hijo:

abstract class Animal
{
  public abstract Food GetFood();

}
class Tiger : Animal
{
  public override Meat GetFood() => ...;
}

Esto nos parece muy loco, porque es un cambio innocuo que aportará poco valor a la hora de la verdad. Y lo que es más importante, ya podemos reproducir este comportamiento actualmente:

abstract class Animal
{
  public abstract Food GetFood();

}
class Tiger : Animal
{
  public override Food GetFood() => new Meat(...);
}

Así que las valoraciones en este caso van a ser un poco duras.

Valoración:

Useful = 1

Crazy = 9

Top-level programs

Cuando queremos crear un programa simple de consola como un ola k ase, en C# tenemos que escribir un montón de movidas absurdas que no ayudan nada. Cosas como una clase o un método estático llamado Main:

using System;

class Program
{
  static void Main()
  {
    Console.WriteLine("ola k ase");
  }
}

Pues esto se acabó. Ahora podemos ponernos a escribir directamente nuestro programa de consola, como si esto fuera node con javascript:

using System;

Console.WriteLine("ola k ase");

Y además nos han solucionado la vida. Esta feature nos permite aceptar casi cualquier sentencia, como el using o un await. Y lo que es mejor, si necesitas los parámetros de entrada, existe una variable llamada args donde por arte de magia los encontrarás.

Bajo nuestro punto de vista, si esta característica no va vinculada a una suerte de C# Script, no aporta demasiado a nuestro código.

Valoración:

Useful = 2

Crazy = 8

Conclusiones

Hemos podido ir jugando con la mayoría de estas features y la verdad es que el protagonista es el nuevo tipo record. Puede ser que debamos empezar a normalizar su uso desde ya mismo, porque parece ser que ha venido a quedarse. Es una suerte de implementación ya medio formada, tipo syntax sugar, muy estética y que nos va a ayudar mucho en nuestro día a día. Y quizá no solo a nivel de tratamiento de datos… si no, al tiempo.

Sobre el resto de características, consideramos que representan una buena evolución sobre lo ya existente. Y como es habitual, alguna ida de olla también nos podemos encontrar.

Los gráficos resumen son los siguientes:

resultados escala sexy-loca

Y dividiendo por la diagonal Vicky Mendoza:

valor según Diagonal Vicky Mendoza

Parece ser que este año tenemos más aprobados que suspensos. O quizá es porque estoy escribiendo esto de vacaciones y me encuentro magnánimo.

Pruébalo tú mismo

Podéis probar todo esto siguiendo estos pasos:

  1. Descargar e instalar [LINQPad6](https://www.linqpad.net/LINQPad6.aspx
  2. Abrir la aplicación.
  3. En el menú dirigirse a Edit > Preferences.
  4. En la ventana que aparece hay que seleccionar el Tab llamado Query.
  5. Marcad la opción de use Roslyn Daily build for experimental C# 9 support.

Configuración de LINQPad6

¿No encuentras los comentarios? más información.