C# intermedio
Este curso de C# intermedio es ideal para seguir ampliando tus conocimientos y convertirte en profesional de uno...
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# 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
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}");
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)));
}
}
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.
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.
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.
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
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.
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” 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;
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();
}
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:
También te puede interesar
Este curso de C# intermedio es ideal para seguir ampliando tus conocimientos y convertirte en profesional de uno...
Si quieres conocer por qué C# es mejor que Java a nivel de performance, te lo contamos en el siguiente video.
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#.