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

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

César Díaz Alcolea
Moderniza tu código con las novedades de C# 8

C# en cada versión que va apareciendo, trae características mejoradas para hacernos a los desarrolladores la vida más fácil. Veamos algunas de las novedades más destacadas y usables incluidas en C# con su versión 8, que se admite en .NET Core 3.x y .NET Standard 2.1. La mayoría de ellas están orientadas a hacer nuestro código más conciso, como podrás ver a continuación.

Using Declarations

Una declaración using no es más que una técnica en la que una variable se declara junto a esta, también le dice al compilador que la variable debe eliminarse al final del ámbito donde se declara (donde se usa esa variable), veamos un ejemplo:

if (...) 
{ 
   using (var file = new FileStream(@"hola.txt")) 
   {
    // statements
   }
}

En el ejemplo anterior, puede ver que variable file se elimina tan pronto como se alcanza la llave de cierre del using.

Se puede escribir el mismo código usando las opciones de C#8 tal que así:

if (...) 
{ 
   using FileStream file = new FileStream(@"hola.txt");
   // statements
}

En el código anterior, podemos ver que el objeto file se elimina después de que el alcance de using llegue a su fin. En este caso al salir del if, pero puede servir por ejemplo para tenerlo en el ámbito de un método.

Consumo de Async Streams

Aunque esto parezca que no es nuevo, se trata de una mejora a la hora de consumir objetos IEnumerable haciendo uso de async, es mejor verlo con un ejemplo de como se hacía hasta ahora:

async Task<IEnumerable<int>> GetMultipleInts()
{
    var numbers = new List<int>();
    int result = -1;
    do 
    {
        result = await GetOneIntAsync();
        numbers.Add(result);
    } while (result > 0);

    return list;
}

En el ejemplo anterior, tenemos que instanciar nuestra lista y añadir a la misma el resultado después de llamar a GetOneIntAsync

Con la nueva opción de C#8 se puede hacer de la siguiente manera:

async IAsyncEnumerable<int> GetMultipleInts()
{
    int result = -1;
    do 
    {
        result = await GetOneIntAsync();
        yield return result;
    } while (result > 0);
}

Como puedes ver, queda más compacto el código ya que no tenemos que estar añadiendo los resultados de la llamada.

Nuevos índices y rangos

C# 8 viene con dos tipos nuevos que se utilizan para recuperar los elementos de una lista / matriz. System.Index y System.Range se utilizan para recuperar esos tipos. ^ denota el signo de índices y .. denota el signo de rango. Vamos a verlo en detalle.

Crear Indices (^)

Generalmente, cuando buscamos los elementos de la matriz, el índice comienza con “0”, pero cuando usamos este nuevo tipo, sus índices comienzan desde el final y van en dirección ascendente.

var array = new string[]
{                 
    "one",         // 0      ^4
    "two",         // 1      ^3
    "three",       // 2      ^2
    "four",        // 3      ^1
};

En el ejemplo anterior, tenemos cuatro elementos en una matriz, en general, el índice comienza con 0 y termina con 3. Pero si hacemos uso de ^, comienza con ^ 4 (final) y llega hasta ^ 1.

Veamos el siguiente ejemplo:

Console.WriteLine("get array " + array[1]);
//get array "two"
Console.WriteLine("get array using indices caret operator " + array[^1]);
//get array using indices caret operator "four"

Crear Rangos(..)

Este operador busca el inicio y el final de sus operandos.

Entonces, si usamos el ejemplo anterior e intentamos obtener valores del array, obtendremos lo siguiente:

foreach (var p in array[..]) Console.Write($"{p} ");
//one two three four 
foreach (var p in array[..3]) Console.Write($"{p} ");
//one two three 
foreach (var p in array[2..]) Console.Write($"{p} ");
//three four
  • En el ejemplo anterior:

    - [..] recupera todos los elementos de la lista
    - [..3] recupera todos los elementos hasta el índice 3
    - [2..] recupera todos los elementos a partir del índice 2

Con esta nueva característica lo que podemos conseguir es hacer este tipo de operaciones sin tener dependencias de Linq si lo vemos interesante para mejorar la legibilidad de nuestro código.

Mejoras en Cadenas Interpoladas

C# 8 ha introducido algunas mejoras en la cadena interpolada.

¿Sabes qué es la “cadena” interpolada?

El $ se usa para identificar la cadena como una cadena interpolada, esta cadena contiene una expresión de interpolación y, además, se reemplaza por el resultado real. Veamos un ejemplo real:

string name = "ABC";
int count = 15;

// String interpolation:
Console.WriteLine($"Hello, {name}! You have {count} apples");

Antes de C# 8, se permitía usar la interpolación de cadenas ($) y el identificador literal (@) juntos pero en una secuencia específica. Significa que cuando se usa $ y @ juntos, es necesario que $ aparezca antes de la @ . Si se usa @ antes de $, el compilador dará un error, como se muestra en el siguiente ejemplo:

int Base = 10; 
int Height = 30; 
int area = (Base * Height) / 2; 

//$ token appears before @ token 
Console.WriteLine("Finding the area of a triangle:"); 
Console.WriteLine($@"Height = ""{Height}"" and Base = ""{Base}"""); 
Console.WriteLine($@"Area = ""{area}"""); 

//Finding the area of a triangle:
//Height = "30" and Base = "10"
//Area = "150"
int Base = 10; 
int Height = 30; 
int area = (Base * Height) / 2; 

// @ token appears before $ token 
// Then the compiler will give an error 
Console.WriteLine("Finding the area of a triangle:"); 
Console.WriteLine(@ $"Height = ""{Height}"" and Base = ""{Base}""");                    
Console.WriteLine(@ $"Area = ""{area}"""); 

//Error
error CS1646: Keyword, identifier, or string expected after verbatim specifier: @...
error CS1646: Keyword, identifier, or string expected after verbatim specifier: @...

Cuando usamos una @ antes del $, el compilador da un error. Este problema se resuelve en C# 8, ahora puede usar una @ antes del $ o $ antes de @ y el compilador no dará ningún error. Como se muestra en el siguiente ejemplo:

int Base = 40; 
int Height = 80; 
int area = (Base * Height) / 2; 
Console.WriteLine("Finding the area of a triangle:"); 

// Here, $ token appears before @ token 
Console.WriteLine($@"Height = ""{Height}"" and Base = ""{Base}"""); 

// Here, @ token appears before $ token 
Console.WriteLine(@$"Area = ""{area}"""); 

//Finding the area of a triangle:
//Height = "80" and Base = "40"
//Area = "1600"

ReadOnly Members

Si desea otorgar una propiedad de solo lectura a cualquier miembro de un struct, en C#8 tenemos la posibilidad de hacerlo decorando sólo el miembro. Veamos un fragmento de código para ver cómo se hacía anteriormente. Esta es la práctica de código general que seguimos para hacer que un miembro del struct sea readonly:

public readonly struct GetSquare
{
    public int InputA { get; set; }
    public int InputB { get; set; }
    public int output => Math.Pow(A,B);

    public override string ToString() =>
        $"The answer is : {output} ";
}

En el código anterior, hemos usado el método Math.Pow para calcular el cuadrado del número de entrada, en lugar de eso en C# 8, podemos hacer que el miembro del struct sea de solo lectura, puesto que es lo que deseamos en este caso, quedaría como el siguiente ejemplo:

public struct GetSquare
{
    public int InputA { get; set; }
    public int InputB { get; set; }
    public readonly int output => Math.Pow(A,B);

    public override string ToString() =>
        $"The answer is : {output} ";
}

Ahora es posible en C# 8, que en lugar de dar al struct completo como de solo lectura, que el miembro del struct sea el que tiene el atributo readonly, indicando que no se puede modificar su estado.

Métodos de interfaz predeterminados

Esta es una buena característica de C# 8, que permite agregar miembros a la interfaz (como métodos) incluso en versiones posteriores sin violar la implementación existente, porque aquí, la implementación existente hereda la implementación predeterminada.

interface IWriter
{
    void Write(string path, string message);
    void Write(Exception ex) => Write("C:\temp", ex.ToString()); // New overload
}

class FileWriter : IWriter
{
    public void Write(string path, string message) { ... }
    // Write(Exception) gets default implementation
}

Funciones estáticas locales

En C# 8, se puede crear una función local como static para ayudar a certificar que la función local no contiene la variable del ámbito. En el siguiente ejemplo una función local intenta acceder a la variable local.

int Add()
{
    int A = 5;    
    LocalFunction();
    return A;

    void LocalFunction() => A + 3;
}

Si hacemos lo anterior, se genera un error que dice:

CS8421una función local estática no puede contener una referencia a .

Para evitar este problema, podemos usar una función static local, en la que podemos reescribir el mismo código con el método:

int Add()
{
    int A = 5;
    return Sum(A);

    static int Sum(int val) => val + 3;
}

Null-coalescing Operator

Todos conocemos la posibilidad de declarar variables que aceptan nulos haciendo uso del operador ?, por ejemplo:

int i = null //error
int? i = null //OK

Con esta nueva funcionalidad lo que se pretende es que si vamos a asignar valores nulos a tipos que si los aceptan como pudiera ser un string, lo marquemos previamente con el símbolo ?. De tal forma que si hacemos un uso indebido de una variable, el compilador nos avise con warnings de un posible null reference. Se ve más claro con un ejemplo:

string a = null; // Warning: Assignment of null to non-nullable reference type
string? b = null; //OK
Console.WriteLine($"The string is {b}"); // Warning: Possible null reference exception
Console.WriteLine($"The string is {b ?? "null" }"); //OK

¿Cómo habilitar tipos de referencia que aceptan valores NULL a nivel de proyecto?

Primero, hay que usar como mínimo C# 8. Hay que abrir el archivo csproj y ver si ya estás usando LangVersion 8.0:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>
</Project>

Si no, lo tendrías que añadir y además añadir enable tal que así:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Habilitar tipos de referencia que aceptan null a nivel de fichero

Existe la posibilidad de hacerlo sólo en los ficheros o parte de ellos que deseemos. Para habilitarlo por archivo, se puede usar #nullable enable donde se quiera habilitar la funcionalidad y #nullable disable donde quiera deshabilitarla. Se puede ver en el siguiente ejemplo:

using System;

public class Program
{
    public static void Main()
    {
        #nullable disable

        Version.TryParse("1.0.0", out var version);
        Console.WriteLine(version.Major);

        #nullable enable

        Version.TryParse("1.0.0", out var version2);
        Console.WriteLine(version2.Major); //deference of a possibly null reference
    }
}

Esto significa que se puede optar por permitir o excluir los tipos de referencia que aceptan valores NULL cuando se desee, lo cual puede resultar muy útil para migrar un código base existente poco a poco y no habilitar esta característica de manera global.

Switch Expression

Hay un cambio en la expresión switch y su sintaxis para la coincidencia de patrones. Supongamos que tenemos un enum con colores de frutas:

public enum Fruits { Red, Green, Blue }

Para hacer uso de switch hasta ahora lo hacíamos de la siguiente manera:

switch (c)
{
         case Fruits.Red:
            Console.WriteLine("The Fruit is red");
            break;
         case Fruits.Green:
            Console.WriteLine("The Fruit is green");
            break;
         case Fruits.Blue:
            Console.WriteLine("The Fruit is blue");
            break;
         default:
            Console.WriteLine("The Fruit is unknown.");
            break;
}

Gracias a C#8 lo podemos hacer de una forma mucho más concisa:

var message = c switch
{
   Fruits.Red => "The Fruit is red",
   Fruits.Green => "The Fruit is green",
   Fruits.Blue => "The Fruit is blue",
        _ => "The Fruit is unknown"
};

Console.WriteLine(message);

- En la sintaxis original, la palabra clave case se usa con la instrucción switch, pero aquí la palabra clave case se reemplaza por =>, que se explica más por sí misma.
- En la sintaxis original, la palabra clave default se usa para el caso predeterminado, pero aquí, la palabra clave predeterminada se reemplaza por el signo_.
- En la sintaxis original, la palabra clave switch se usa antes de la variable, pero aquí viene después de la variable.

Implicit constructors

Esta es una nueva mejora del lenguaje que va orientada como muchas de las que hemos visto anteriormente a hacer que el código sea más conciso. Imagínate un código como el siguiente, pero con muchas líneas de este tipo

Person[] people =
{
    new People("David", "Martinez", "Perez"),
    new People("Juan", "Perez", "Martinez"),
    new People("Antonio", "Martin", "Mora"),
    ...,
};

Con las nuevas características de C# 8 se podría escribir tal que así:

Person[] people =
{
    new ("David", "Martinez", "Pérez"),
    new ("Juan", "Pérez", "Martinez"),
    new ("Antonio", "Martin", "Mora"),
    ...,
};

Lo que se hace es inferir el tipo del constructor para no tener que repetirlo por cada línea, que para unas pocas no se nota tanto, pero si tienes muchas es código que te ahorras de escribir.

Y hasta aquí algunas de las novedades más destacadas y usables para poder darle a tu código C# una actualización para hacerlo más conciso. Viene bien que conozcas estas posibilidades del lenguaje puesto que seguramente con el tiempo te encuentres código que haga uso de esta sintaxis.

Relacionado

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

Por qué aprender C#

Por qué aprender C#

Lenguajes de programación

28 de Enero de 2019

Conoce los 7 principales motivos por los que resulta muy recomendable comenzar a desarrollar en C#.

Curso de C# para principiantes

Curso de C# para principiantes

curso

Con este curso aprenderás:

  • Aprenderás a dominar el lenguaje de C# desde 0
  • Aprenderás a usar Visual Studio
  • Conceptos básicos de programación y cómo se aplican en C#

Duración: 3 horas y 29 minutos

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

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