Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=b3614c2e-01c0-49c5-ae39-feca5256b42b

C# 12: todo lo nuevo del lenguaje aparecido con .NET 8

Imagen ornamental generada por campusMVP

C# 12 es la última versión, recién aparecida, del principal lenguaje de programación de .NET 8. Como cada año y nueva versión, presenta varias novedades interesantes a las que podemos sacarle partido en el día a día.

En este post vamos a ver todas las nuevas características una a una para que estés al día de las novedades.

¡Vamos allá!

Constructores primarios

Los constructores primarios te permiten declarar parámetros para una clase o estructura en la misma línea en la que declaramos el propio tipo.

Así, en lugar de crear un constructor, podemos utilizar estos parámetros en todo el cuerpo del tipo para inicializar campos o propiedades. Por ejemplo, así definiríamos una clase de la manera convencional:

public class Caja {

    public Caja(string nombre, int ancho, int alto, int largo) {
        Nombre = nombre;
        Ancho = ancho;
        Alto = alto;
        Largo = largo;
    }

    public string Nombre { get; }
    public int Ancho { get; }
    public int Alto { get; }
    public int Largo { get; }

    public int Volumen {
        get {
            return Ancho * Alto * Largo;
        }
    }

    public override string ToString() {
        return $"Hola, ¡soy la caja {Nombre}!";
    }
}

En este caso tenemos una clase Caja y hemos definido su constructor, 5 propiedades y un método. En el constructor mapeamos los parámetros a las propiedades, que posteriormente son de solo lectura (solo se pueden establecer en el constructor). 24 líneas en total.

Si usamos la nueva característica de constructores primarios, la clase quedaría mucho más escueta así:

class Caja(string nombre, int ancho, int alto, int largo)
{
    public string Nombre => nombre;
    public int Ancho => ancho;
    public int Alto => alto;
    public int Largo => largo;
    public int Volumen => ancho * alto * largo;
    public override string ToString() => $"Hola, ¡soy la caja {nombre}!";
}

¡9 líneas! vale, aquí he usado expresiones lambda para todo y antes no, pero lo que quiero mostrar es que realmente nos permiten crear nuestras clases y estructuras de manera mucho más rápida y escueta, escribiendo mucho menos código: declaramos los parámetros del constructor a continuación del nombre de la clase, y luego los usamos dentro tal cual.

Fíjate en que podemos usar esos parámetros del constructor primario en cualquier parte del cuerpo de la clase. Por ejemplo, en el método ToString() que he sobrescrito lo estoy utilizando en lugar de la propiedad para que veas que está disponible incluso en el cuerpo de un método, lo mismo que en el cuerpo de una propiedad.

Instanciamos la clase de la manera convencional:

var caja = new Caja("Caja de cartón", 10, 10, 20);

Incluso podemos usar este constructor primario desde otros constructores. Por ejemplo, si quisiésemos definir un constructor sin parámetros podríamos añadirlo así:

public Caja():this("Sin nombre",0,0,0) {}

que nos permitiría crear cajas sin nombre y con 0 en todas las dimensiones con un simple new Caja(). Fíjate en que usamos el constructor primario usando la palabra clave this y un par de llaves para denotar que el constructor no tiene cuerpo, como con cualquier otro constructor que hace uso de otro.

Expresiones de colección

Antes de C# 12, inicializar colecciones con valores requería una sintaxis diferente según el tipo de colección que fuésemos a inicializar. Por ejemplo:

int[] x1 = new int[] { 1, 2, 3, 4 };
int[] x2 = Array.Empty<int>();
WriteByteArray(new[] { (byte)1, (byte)2, (byte)3 });
List<int> x4 = new() { 1, 2, 3, 4 };
Span<DateTime> dates = stackalloc DateTime[] { GetDate(0), GetDate(1) };

Las expresiones de colección son una nueva sintaxis más concisa y coherente a la hora de crear valores comunes en colecciones. Se pueden utilizar para inicializar arrays, colecciones de System.Span<T>, System.ReadOnlySpan<T> y tipos que admiten inicializadores de colecciones, como por ejemplo List<T>.

Con ellas unificamos la manera de crear todos esos arrays y colecciones. El código equivalente al anterior, pero usando expresiones de colección, quedaría de la siguiente forma:

int[] x1 = [1, 2, 3, 4];
int[] x2 = [];
WriteByteArray([1, 2, 3]);
List<int> x4 = [1, 2, 3, 4];
Span<DateTime> dates = [GetDate(0), GetDate(1)];

Como ves, mucho más sencilla y directa y, además, idéntica para todos los tipos de datos soportados.

Otra capacidad interesante de estas expresiones es que permiten utilizar el operador "spread" (u operador de propagación), los dos puntitos .., para rellenar las nuevas colecciones desde otras prexistentes:

int[] numeros1 = [1, 2, 3];
int[] numeros2 = [4, 5, 6];
int[] masNumeros = [.. numeros1, .. numeros2, 7, 8, 9];
// masNumeros contiene [1, 2, 3, 4, 5, 6, 7, 8, ,9];

Como la guinda del pastel de esta funcionalidad, el código con expresiones de colección es más rápido y eficiente que el que puedas escribir con la sintaxis anterior, ya que el compilador establece automáticamente la capacidad de la colección y evita el copiado de datos, almacenándolos directamente sin repetirlos.

Alias para cualquier tipo, con using

La palabra clave using ahora tiene una nueva utilidad aparte de las que ya conocemos de toda la vida: definir al vuelo alias para tipos existentes o nuevos.

En C# 12 podemos definir un nuevo nombre para un tipo o una estructura que queramos utilizar simplemente poniéndole la plabra clave using delante. Por ejemplo:

using arrEnteros = int[];
using arr2D = int[][];
using Punto = (int x, int y);

En este pequeño fragmento acabo de definir 3 alias para referirme a arrays de números enteros, matrices con doble índice (arrays 2D) y una estructura formada por dos coordenadas que definen un punto. Sin tener que crear nuevos tipos para ello.

Ahora puedo utilizarlas en el código de manera directa sin usar un new ni nada similar:

arrEnteros a1 = [0, 1, 2, 3, 4];
arr2D a2 = [[0, 1], [2, 3], [4, 5]];
Punto p1 = (1, 2);

creando una nueva instancia de cada uno de estos alias.

Es otro atajo en el código para ir más rápido y escribir menos, aunque la verdad, C# con estas cosas se parece más un lenguaje de script (que por suerte, no es).

Parámetros opcionales en expresiones lambda

Las expresiones lambda son esencialmente funciones anónimas y, como tales, tiene parámetros y un cuerpo que las define, solo que se crean con una sintaxis mucho más sencilla. Una limitación que tenían hasta ahora es que no se podían utilizar con parámetros opcionales, aunque en las funciones convencionales sí era posible desde hace décadas.

Ahora podemos establecer valores por defecto para parámetros opcionales con la sintaxis habitual, por ejemplo:

var multiplica = (int num, int factor = 1) => num*factor;
Console.WriteLine( multiplica(5) ); //Muestra un 5

Al igual que en una función convencional le indicamos con un = el valor por defecto para un parámetro cuando no se le pasa a la función. Sigue las mismas reglas que cualquier parámetro opcional de otra función.

Parámetros con modificador ref readonly

Ahora es posible utilizar el nuevo modificador ref readonly para los parámetros de un método, de modo que indicaremos que se pasan por referencia, pero al mismo tiempo que no se pueden modificar:

static int Maximo(ref readonly int a, ref readonly int b) {
        return (a > b ? ref a : ref b);
}

var primero = 0;
var segundo = 5;

var max = Maximo(ref primero, ref segundo); //5

Si intentásemos modificar el valor de esos dos parámetros dentro de la función se produciría un error de compilación:

Error al intentar asignar un valor a uno de los parámetros

¿Y para qué necesitamos esto si ya tenemos el modificador in desde hace años?

Pues porque esta nueva opción permite mejorar el rendimiento del código al evitar copias innecesarias de datos y, al mismo tiempo, imponer la inmutabilidad de los argumentos.

Este es un nuevo modificador que usarás en ocasiones muy concretas y que se puede considerar como avanzado. Puedes ver en este enlace la referencia completa y la justificación de ref readonly. Prepara un café...

Atributos experimentales

Si creas bibliotecas que van a usar otros desarrolladores, puede que te interese marcar ciertas nuevas características como experimentales, de modo que quizá las cambies o las elimines por completo en el futuro.

Para cubrir esta necesidad (que seguramente tiene a menudo el equipo de .NET), hay un nuevo atributo dentro del espacio de nombres System.Diagnostics.CodeAnalysis, llamado ExperimentalAttribute.

Puedes marcar clases y métodos con el atributo:

[ExperimentalAttribute]
public class MiClaseExperimental {
    [ExperimentalAttribute]
    public void miMetodoExperimental() {
        //Código
    }
}

Si ahora alguien utiliza la clase o el método marcados de esta manera en tu código, el compilador le mostrará automáticamente un aviso de que son experimentales para que sea consciente de que en el futuro quizá salte todo por los aires.

Otras cosas avanzadas

Quedan todavía dos características más que se han añadido al lenguaje y que, realmente, tienen una aplicación muy limitada, y están pensadas fundamentalmente para que los use el equipo de .NET. No entraré en detalles sobre ellas pero te dejo enlaces para profundizar:

  • Matrices inline: permite definir clases que van a actuar como matrices y que están preubicadas en memoria según su tamaño. Las utilizan internamente para mejorar el rendimiento de las clases tipo Span<T> y similares.
  • Interceptores: es una característica experimental que debes activar explícitamente (activando el feature flag InterceptorsPreview en tu archivo de proyecto) y que se parece bastante en la idea a los Proxy de ECMAScript. Básicamente permiten interceptar en tiempo de compilación las llamadas a ciertos métodos para que realmente llamen a métodos distintos, sustituyendo así su funcionalidad. Es algo que está pensado para modificar funcionalidad existente en generadores de código y cosas así. Parece otra cosa hecha muy ah hoc para las necesidades del equipo de .NET.

Las meto por completitud, pero no creo que sean interesantes para el programador en general.

Con esto hemos visto todas las novedades que trae el lenguaje C# en su versión 12, aparecida junto a .NET 8.

¡Espero que te haya resultado útil!

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón
Archivado en: Lenguajes y plataformas

¿Te ha gustado este post?
Pues espera a ver nuestro boletín...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.