Las cookies nos permiten ofrecer nuestros servicios. Al utilizar nuestros servicios, aceptas el uso que hacemos de las cookies. Más Información. Aceptar

Novedades y cambios incluídos en C# 9

César Díaz Alcolea

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 que versión tenemos.

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

Imagen 1 en Novedades y cambios incluídos en C# 9También tenemos que tener instalado el SDK v5 tanto el SDK para poder construir aplicaciones como el Runtime para poder ejecutarlas.

image-20201116143628838

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

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

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

Relacionado

Te dejamos una selección de cursos, carreras y artículos

Curso de C# intermedio

Curso de C# intermedio

curso

Con este curso aprenderás:

  • Aprenderás a dominar los sistemas de UI relacionados con .Net
  • Serás capaz de crear aplicación de escritorio con Winforms, Consola y WPF
  • Aprenderás a usar Visual Studio

Duración: 3 horas y 19 minutos

Performance C# VS JAVA

Performance C# VS JAVA

Lenguajes de programación

30 de Enero de 2019

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

Moderniza tu código con las novedades de C# 8

Moderniza tu código con las novedades de C# 8

Lenguajes de programación

20 de Noviembre de 2020

En este artículo te detallamos todos los cambios y novedades que se incluyen en la versión 8.0 del lenguaje de programación C#.

Más de 300 empresas confían en nosotros

Oesia
Vass
Everis
Ayesa
Altran
Ibermatica
Atmira
GFI
Accenture
GMV
Concatel
Telefonica
Caser
Banco de España
kpmg
Mapfre
Randstad