OpenWebinars

Lenguajes de Programación

Novedades y cambios incluídos en C# 9

Conoce todas las novedades y los cambios más importantes que nos ofrece la última versión 9.0 del lenguaje de programación C#.

 César Díaz Alcolea

César Díaz Alcolea

EXPERTO DESARROLLO DE APLICACIONES

Lectura 6 minutos

Publicado el 4 de enero de 2021

Compartir

C# 9.0 ya está disponible para su uso, y me gustaría compartir algunas de las principales características que trae esta nueva versión, para mostrarte cómo puedes seguir modernizando tu código tal y como vimos en el artículo Moderniza tu código con las novedades de C# 8

Para poder usar esas últimas características hay que actualizar Visual Studio a la versión 16.8.1 o superior, para ello podemos arrancar Visual Studio Installer y comprobar qué versión tenemos.

También tenemos que tener instalado el SDK v5 tanto el SDK para poder construir aplicaciones como el Runtime para poder ejecutarlas.

Con cada nueva versión de C# se está intentando una mayor claridad y simplicidad en los escenarios comunes de codificación, y C# 9.0 no es una excepción.

C# 9.0 es compatible con .NET 5 y para poder empezar a utilizar las siguientes características, una vez lo tengas disponible en tu máquina tan sólo tienes que cambiar lo siguiente en tu csproj:

<Project>
 <PropertyGroup>
   <LangVersion>preview</LangVersion>
   <TargetFramework>net5.0</TargetFramework>
 </PropertyGroup>
</Project>

O bien cambiarlo desde las propiedades del proyecto

Imagen 2 en Novedades y cambios incluídos en C# 9

Init-only Properties

Los inicializadores de objetos nos dan la posibilidad de crear objetos inmutables cuyas propiedades sólo podrán ser seteadas en su constructor (cuando el objeto es inicializado), por ejemplo:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Como puedes ver, tenemos una clase Person con dos propiedades en las cuales en vez de tener nuestro típico set lo que tenemos es init, y nada más, no tenemos que crear un constructor. Lo cual nos va a permitir crear nuestros objetos con la siguiente sintaxis:

Person person1 = new Person { FirstName = "Antonio", LastName = "Martínez" };
Person person2 = new Person { FirstName = "Juan", LastName = "Díaz" };

Console.WriteLine($"Person 1 is {person1.FirstName} {person1.LastName}");
Console.WriteLine($"Person 2 is {person2.FirstName} {person2.LastName}");

Conviértete en un Backend Developer
Domina los lenguajes de programación más demandados. Accede a cursos, talleres y laboratorios para crear proyectos con Java, Python, PHP, Microsoft .NET y más
Comenzar gratis ahora

ReadOnly Fields with Init Accesors

Debido a esta nueva sintaxis, sólo podemos cambiar nuestros campos cuando los inicializamos, lo cual es muy parecido a usar readonly. Y si lo intentas, te va a dar un error indicando que el campo es readonly.

Así que si queremos cambiar estos campos readonly sólo lo vamos a poder hacer haciendo uso de init. Antes podíamos hacerlo desde el constructor, pero ahora podemos hacerlo usando init de la siguiente manera:

public class Person
{
    private readonly string firstName;
    private readonly string lastName;

    public string FirstName 
    { 
        get => firstName; 
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName 
    { 
        get => lastName; 
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

Records

Las propiedades init nos vienen muy bien si queremos hacer que las propiedades individuales sean inmutables. Pero si queremos que todo el objeto sea inmutable y se comporte como un valor, entonces tenemos que declararlo como un record:

public record Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Person person1 = new Person { FirstName = "Pepe", LastName = "Martínez" };
Person person2 = new Person { FirstName = "Juan", LastName = "Díaz" };

Console.WriteLine($"Person 1 is {person1.FirstName} {person1.LastName}");
Console.WriteLine($"Person 2 is {person2.FirstName} {person2.LastName}");

Si te das cuenta, lo único que hemos hecho ha sido marcar la clase con record lo cual nos va a permitir utilizar nuevas funcionalidades puesto que la idea es que tu objeto decorado de tal forma sea inmutable con lo que si queremos hacer cambios en este objeto vamos a tener que hacer una copia del mismo, te lo explico a continuación.

Expresions With using Records

Cuando se trabaja con datos inmutables, una de las prácticas más comunes es crear nuevos valores desde los que ya tenemos para representar un nuevo estado. Por ejemplo, si nuestra persona cambiara su apellido lo representaríamos como un nuevo objeto que es una copia del antiguo, excepto con un apellido diferente. En lugar de representar a la persona a lo largo del tiempo, el record representa el estado de la persona en un momento dado.

Para ayudar a este estilo de programación, los record permiten un nuevo tipo de expresión, la expresión with:

var otherPerson = person1 with { LastName = "García" };
Console.WriteLine($"Person 1 is {otherPerson.FirstName} {person1.LastName}");

with usa la sintaxis de inicialización de objetos para establecer qué es diferente en el nuevo objeto con respecto al antiguo. Se pueden especificar múltiples propiedades si así se desea para cambiarlas todas de una vez.

¿Para qué podemos necesitar esto?

Cuando creamos tipos inmutables y queremos proteger nuestra clase contra modificaciones externas, no queremos dejar a nuestros setters expuestos hacia afuera. Gracias a esta nueva sintaxis podemos hacerlo de una manera sencilla y concisa.

Top-level programs

Escribir cualquier programa sencillo de C# nos requiere meter un código como el siguiente:

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

Hay una cierta cantidad de código que rodea incluso al más simple de los programas, debido a la necesidad de un método main explícito. Por lo tanto, el objetivo principal de esta característica es permitir que los programas C# no estén rodeados de un innecesario “boilerplate”, por el bien de los estudiantes y la claridad del código. Con lo que el programa anterior podría quedar tan sencillo como esto:

using System;

Console.WriteLine("Hello World!");

O aún más sencillo:

System.Console.WriteLine("Hello World!");

Esta es una característica relativamente menor que hace que el C# sea más accesible para los programadores novatos, en teoría. Visual Studio sigue generándolo todo, incluyendo espacios de nombres y una definición de clase por defecto, pero teóricamente podrías escribir un par de líneas de C# junto con las directivas using apropiadas, y ejecutar tu programa.

Mejoras en Pattern Matching

En la versión anterior de C#, se introdujo una nueva sintaxis para switch que nos permitió realizar la coincidencia de patrones en las propiedades de un tipo:

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 }         => "origin",
    Point { X: var x, Y: var y } => $"({x}, {y})",
    _                            => "unknown"
};

Digamos que queremos usar la comparación de patrones para determinar el precio de la entrada para una persona que visite el zoológico. Dependiendo de su edad, o pagan el precio completo, o reciben un descuento. En la C# 8, podrías expresar esto como:

static int CalculateTicketPrice(Person person)
{
    return person switch
    {
        var p when p.Age <= 12 => 5,
        var p when p.Age > 12 && p.Age <= 60 => 15,
        var p when p.Age > 60 => 10
    };
}

Fíjate que hay bastante ruido en nuestro código, aunque nuestro switch sólo trabaja realmente la propiedad p.Age de nuestra clase.

Con C# 9, las expresiones de los switch pueden ser relacionales. Esto significa que pueden funcionar con operadores relacionales (como <= < > >= …), como te voy a mostrar a continuación

Relational Patterns

C# 9.0 introduce patrones correspondientes a los operadores relacionales <, <= >, >=. Así podemos simplificar el código anterior a algo como lo siguiente:

static int CalculateTicketPrice(Person person)
{
    return person.Age switch
    {
        <= 12 => 5,
        > 12 => 15,
        > 60 => 10
    };
}

Aquí <= 12 >12 >60 son los patrones relacionales.

Logic Patterns

Finalmente se pueden combinar patrones con operadores lógicos and, or y not, deletreados como palabras para evitar la confusión con los operadores utilizados en las expresiones . Por ejemplo, modificando el ejemplo anterior podíamos tener algo como lo siguiente:

static int CalculateTicketPrice(Person person)
{
    return person.Age switch
    {
        <= 12 => 5,
        > 12 and <= 60 => 15,
        > 60 => 10
    };
}

Un uso común not será aplicarlo para comprobar nulls. Por ejemplo:

null => throw new ArgumentNullException("Invalid Age"),
not null => 20

También not será conveniente en los if que contienen expresiones tipo “es”:

if (!(e is Customer)) { ... }

Lo podemos escribir como:

if (e is not Customer) { ... }

Target Typing

“Target typing” es un término que utilizamos cuando una expresión obtiene su tipo del contexto en el que se utiliza. Las expresiones new en C# siempre han requerido que se especifique un tipo. Ahora se puede omitir el tipo si hay un tipo claro al que se asignan las expresiones.

Point p = new (3, 5);

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

Person person = student ?? customer;

Covariant returns

Actualmente, cuando se anula un método de una clase base, el método anulado debe devolver el mismo tipo que el método de la clase base. En algunas situaciones, sería más práctico devolver un tipo más específico. C# 9 lo hace posible al permitir que los métodos de anulación devuelvan un tipo que se deriva del tipo de retorno del método base:

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

Mejora las habilidades de tus desarrolladores
Acelera la formación tecnológica de tus equipos con OpenWebinars. Desarrolla tu estrategia de atracción, fidelización y crecimiento de tus profesionales con el menor esfuerzo.
Solicitar más información

Parameter Null-Checking

Esta característica introduce una sintaxis simple para automatizar las comprobaciones de nulos de los parámetros del método. Por ejemplo, este código:

public string SayHello(string name)
{
    if (name == null)
        throw new ArgumentNullException(nameof(name));
    return $"Hello {name}";
}

Puede simplificarse a esto:

public string SayHello(string name!) => $"Hello {name}";

El ! después del nombre del parámetro inserta automáticamente un control nulo para ese parámetro.

La mayoría de las características introducidas por C# 9 son relativamente pequeñas, diseñadas para hacer el código más simple, menos desordenado y más legible; son muy convenientes, pero probablemente no cambien la forma en que escribimos el código de una manera muy profunda. Los records sin embargo hacen que sea mucho más fácil y escribir tipos inmutables.

Si quieres conocer un poco más del lenguaje C# te recomiendo los siguientes recursos para que empieces a trabajar con él o amplíes tus conocimientos:

Curso de C# para principiantes

Curso de C# intermedio

Compartir este post

También te puede interesar

Icono de la tecnología
Curso

C# intermedio

Intermedio
3 h. y 19 min.

Este curso de C# intermedio es ideal para seguir ampliando tus conocimientos y convertirte en profesional de uno...

José Manuel Montero Ortega
4
Performance C# VS JAVA
Blog

Performance C# VS JAVA

Si quieres conocer por qué C# es mejor que Java a nivel de performance, te lo contamos en el siguiente video.

José Manuel Montero Ortega