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.
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.
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:
Y dividiendo por la 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:
- Descargar e instalar [LINQPad6](https://www.linqpad.net/LINQPad6.aspx
- Abrir la aplicación.
- En el menú dirigirse a Edit > Preferences.
- En la ventana que aparece hay que seleccionar el Tab llamado Query.
- Marcad la opción de use Roslyn Daily build for experimental C# 9 support.