Novedades de C# 10

C# 10 agrega las características y las mejoras al lenguaje C# siguientes:

Hay más características disponibles en el modo de vista previa. Se recomienda probar estas características y proporcionar comentarios sobre ellas. Pueden cambiar antes de su versión final. Para poder usar estas características, debe establecer <LangVersion> en Preview en el proyecto. Encontrará información sobre los atributos genéricos más adelante en este artículo.

C# 10 es compatible con .NET 6. Para obtener más información, vea Control de versiones del lenguaje C#.

Puede descargar el SDK de .NET 6 más reciente de la página de descargas de .NET. También puede descargar Visual Studio 2022 Preview, que incluye el SDK de .NET 6.

Structs de registro

Puede declarar los registros de tipo de valor mediante las declaraciones record struct o readonly record struct. Ahora puede aclarar que un elemento record es un tipo de referencia con la declaración record class.

Mejoras de tipos de estructura

C# 10 presenta las mejoras siguientes relacionadas con los tipos de estructura:

Controlador de cadena interpolada

Puede crear un tipo que compile la cadena resultante a partir de una expresión de cadena interpolada. Las bibliotecas de .NET usan esta característica en muchas API. Puede compilar una siguiendo este tutorial.

Directivas using globales

Puede agregar el modificador global a cualquier directiva using para indicar al compilador que la directiva se aplica a todos los archivos de código fuente de la compilación. Normalmente, se trata de todos los archivos de código fuente de un proyecto.

Declaración de espacios de nombres con ámbito de archivo

Puede usar una nueva forma de la declaración namespace para declarar que todas las declaraciones posteriores son miembros del espacio de nombres declarado:

namespace MyNamespace;

Esta nueva sintaxis ahorra espacio horizontal y vertical para las declaraciones namespace más comunes.

Patrones de propiedades extendidos

A partir de C# 10, puede hacer referencia a propiedades o campos anidados en un patrón de propiedad. Por ejemplo, un patrón con el formato

{ Prop1.Prop2: pattern }

es válido en C# 10 y versiones posteriores, y equivalente a

{ Prop1: { Prop2: pattern } }

válido en C# 8.0 y versiones posteriores.

Para obtener más información, consulte la nota de propuesta de características Patrones de propiedades extendidos. Para obtener más información sobre un patrón de propiedades, vea la sección Patrón de propiedad del artículo Patrones.

Mejoras de expresiones lambda

C# 10 incluye muchas mejoras en el modo en que se controlan las expresiones lambda:

  • Las expresiones lambda pueden tener un tipo natural, donde el compilador puede deducir un tipo delegado de la expresión lambda o del grupo de métodos.
  • Las expresiones lambda pueden declarar un tipo de valor devuelto cuando el compilador no puede deducirlo.
  • Los atributos se pueden aplicar a las expresiones lambda.

Estas características hacen que las expresiones lambda sean parecidas a los métodos y las funciones locales. Facilitan el uso de expresiones lambda sin declarar una variable de un tipo delegado y funcionan de forma más fluida con las nuevas API mínimas de ASP.NET CORE.

Cadenas interpoladas constantes

En C# 10, las cadenas const se pueden inicializar mediante la interpolación de cadenas si todos los marcadores de posición son cadenas constantes. La interpolación de cadenas puede crear cadenas constantes más legibles a medida que se compilan las cadenas constantes usadas en la aplicación. Las expresiones de marcador de posición no pueden ser constantes numéricas porque esas constantes se convierten en cadenas en tiempo de ejecución. La referencia cultural actual puede afectar a su representación de cadena. Obtenga más información en la referencia del lenguaje sobre expresiones const.

Los tipos de registro pueden sellar ToString

En C# 10, puede agregar el modificador sealed al invalidar ToString en un tipo de registro. Al sellar el método ToString, se impide que el compilador sintetice un método ToString para cualquier tipo de registro derivado. Un elemento sealed ToString garantiza que todos los tipos de registros derivados usen el método ToString definido en un tipo de registro base común. Puede obtener más información sobre esta característica en el artículo sobre registros.

Asignación y declaración en la misma desconstrucción

Este cambio quita una restricción de versiones anteriores de C#. Anteriormente, una desconstrucción podía asignar todos los valores a variables existentes, o bien inicializar variables recién declaradas:

// Initialization:
(int x, int y) = point;

// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;

En C# 10 se elimina esta restricción:

int x = 0;
(x, int y) = point;

Asignación definitiva mejorada

Antes de C# 10, había muchos escenarios en los que la asignación definitiva y el análisis de estado NULL generaban advertencias que eran falsos positivos. Por lo general, estas implicaban comparaciones con constantes booleanas, el acceso a una variable solo en las instrucciones true o false de una instrucción if, y expresiones de uso combinado de NULL. Estos ejemplos generaron advertencias en versiones anteriores de C#, pero no en C# 10:

string representation = "N/A";
if ((c != null && c.GetDependentValue(out object obj)) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
   representation = obj.ToString(); // undesired error
}

El impacto principal de esta mejora es que las advertencias para la asignación definitiva y el análisis de estado NULL son más precisas.

Se permite el atributo AsyncMethodBuilder en los métodos

En C# 10 y versiones posteriores, puede especificar un generador de métodos asincrónicos diferente para un único método, además de especificar el tipo de generador de métodos para todos los métodos que devuelven un tipo concreto similar a una tarea. Un generador de métodos asíncronos personalizado permite escenarios avanzados de optimización del rendimiento en los que un método determinado puede beneficiarse de un generador personalizado.

Para obtener más información, vea la sección sobre AsyncMethodBuilder del artículo sobre los atributos leídos por el compilador.

Diagnóstico del atributo CallerArgumentExpression

Puede usar System.Runtime.CompilerServices.CallerArgumentExpressionAttribute para especificar un parámetro que el compilador reemplace por la representación de texto de otro argumento. Esta característica permite a las bibliotecas crear diagnósticos más específicos. El código siguiente prueba una condición. Si la condición es false, el mensaje de excepción incluye la representación de texto del argumento pasado a condition:

public static void Validate(bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
    if (!condition)
    {
        throw new InvalidOperationException($"Argument failed validation: <{message}>");
    }
}

Puede obtener más información sobre esta característica en el artículo sobre Atributos de información del autor de la llamada en la sección de referencia del lenguaje.

Pragma #line mejorado

C# 10 admite un formato nuevo para el pragma #line. Es probable que no use el formato nuevo, pero verá sus efectos. Las mejoras permiten una salida más detallada en lenguajes específicos de dominio (DSL), como Razor. El motor de Razor usa estas mejoras para mejorar la experiencia de depuración. Encontrará que los depuradores pueden resaltar el origen de Razor con más precisión. Para obtener más información sobre la nueva sintaxis, vea el artículo sobre Directivas de preprocesador en la referencia del lenguaje. También puede leer la especificación de la característica para obtener ejemplos basados en Razor.

Atributos genéricos

Importante

Los atributos genéricos son una característica en vista previa. Debe establecer <LangVersion> en Preview para habilitar esta característica. Esta característica puede cambiar antes de su versión final.

Puede declarar una clase genérica cuya clase base sea System.Attribute. Esto proporciona una sintaxis más cómoda de los atributos que requieren un parámetro System.Type. Antes había que crear un atributo que tomara Type como parámetro de constructor:

public class TypeAttribute : Attribute
{
   public TypeAttribute(Type t) => ParamType = t;

   public Type ParamType { get; }
}

Y, para aplicar el atributo, se usa el operador typeof:

[TypeAttribute(typeof(string))] 
public string Method() => default;

Con esta nueva característica, puede crear un atributo genérico en su lugar:

public class GenericAttribute<T> : Attribute { }

Luego, especifique el parámetro de tipo para usar el atributo:

[GenericAttribute<string>()]
public string Method() => default;

Puede aplicar un atributo genérico construido completamente cerrado; en otras palabras, se deben especificar todos los parámetros de tipo. Por ejemplo, la siguiente vista no se admite:

public class GenericType<T>
{
   [GenericAttribute<T>()] // Not allowed! generic attributes must be fully closed types.
   public string Method() => default;
}

Los argumentos de tipo deben cumplir las mismas restricciones que el operador typeof. No se permiten los tipos que requieren anotaciones de metadatos. Entre otros, se incluyen los siguientes ejemplos:

  • dynamic
  • nint, nuint
  • string? (o cualquier tipo de referencia que acepte valores NULL)
  • (int X, int Y) (o cualquier otro tipo de tupla que use la sintaxis de tupla de C#)

Estos tipos no se representan directamente en los metadatos e incluyen anotaciones que describen el tipo. En todos los casos, se puede usar el tipo subyacente en su lugar:

  • object para dynamic
  • IntPtr en lugar de nint o unint
  • string en lugar de string?.
  • ValueTuple<int, int> en lugar de (int X, int Y).