Novedades de C# 9.0What's new in C# 9.0

C# 9.0 agrega las siguientes características y mejoras al lenguaje C#:C# 9.0 adds the following features and enhancements to the C# language:

  • RegistrosRecords
  • Establecedores de solo inicializaciónInit only setters
  • Instrucciones de nivel superiorTop-level statements
  • Mejoras de coincidencia de patronesPattern matching enhancements
  • Enteros con tamaño nativosNative sized integers
  • Punteros de funciónFunction pointers
  • Supresión de la emisión de la marca localsinitSuppress emitting localsinit flag
  • Expresiones nuevas con tipo de destinoTarget-typed new expressions
  • Funciones anónimas estáticasstatic anonymous functions
  • Expresiones condicionales con tipo de destinoTarget-typed conditional expressions
  • Tipos de valor devueltos de covarianteCovariant return types
  • Compatibilidad con extensiones GetEnumerator para bucles foreachExtension GetEnumerator support for foreach loops
  • Parámetros de descarte lambdaLambda discard parameters
  • Atributos en funciones localesAttributes on local functions
  • Inicializadores de móduloModule initializers
  • Nuevas características para métodos parcialesNew features for partial methods

C# 9.0 es compatible con .NET 5.C# 9.0 is supported on .NET 5. Para obtener más información, vea Control de versiones del lenguaje C#.For more information, see C# language versioning.

Puede descargar el SDK de .NET más reciente de la página de descargas de .NET.You can download the latest .NET SDK from the .NET downloads page.

Tipos de registroRecord types

En C# 9.0 se presentan los tipos de registro, un tipo de referencia que ofrece métodos sintetizados para proporcionar semántica de valores para la igualdad.C# 9.0 introduces *record types _, which are a reference type that provides synthesized methods to provide value semantics for equality. Los registros son inmutables de forma predeterminada.Records are immutable by default.

Los tipos de registro facilitan la creación de tipos de referencia inmutables en .NET.Record types make it easy to create immutable reference types in .NET. Históricamente, los tipos de .NET se han clasificado principalmente como tipos de referencia (incluidas las clases y los tipos anónimos) y tipos de valor (incluidas las estructuras y tuplas).Historically, .NET types are largely classified as reference types (including classes and anonymous types) and value types (including structs and tuples). Aunque se recomiendan los tipos de valor inmutables, los tipos de valor mutable no suelen generar errores.While immutable value types are recommended, mutable value types don’t often introduce errors. Las variables de tipo de valor contienen los valores para que los cambios se realicen en una copia de los datos originales cuando los tipos de valor se pasan a los métodos.Value type variables hold the values so changes are made to a copy of the original data when value types are passed to methods.

Los tipos de referencia inmutables también ofrecen muchas ventajas.There are many advantages to immutable reference types as well. Estas ventajas son más evidentes en programas simultáneos con datos compartidos.These advantages are more pronounced in concurrent programs with shared data. Desafortunadamente, C# le ha obligado a escribir código adicional para crear tipos de referencia inmutables.Unfortunately, C# forced you to write quite a bit of extra code to create immutable reference types. Los registros proporcionan una declaración de tipos para un tipo de referencia inmutable que usa la semántica de valores para la igualdad.Records provide a type declaration for an immutable reference type that uses value semantics for equality. Los métodos sintetizados para los códigos de igualdad y hash consideran que dos registros son iguales si sus propiedades son iguales.The synthesized methods for equality and hash codes consider two records equal if their properties are all equal. Considere esta definición:Consider this definition:

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

La definición del registro crea un tipo Person que contiene dos propiedades de solo lectura: FirstName y LastName.The record definition creates a Person type that contains two readonly properties: FirstName and LastName. El tipo Person es un tipo de referencia.The Person type is a reference type. Si ha examinado el lenguaje intermedio, es una clase.If you looked at the IL, it’s a class. Es inmutable porque ninguna de las propiedades se puede modificar una vez que se ha creado.It’s immutable in that none of the properties can be modified once it's been created. Al definir un tipo de registro, el compilador sintetiza otros métodos de forma automática:When you define a record type, the compiler synthesizes several other methods for you:

  • Métodos para comparaciones de igualdad basadas en valoresMethods for value-based equality comparisons
  • Invalidación de GetHashCode()Override for GetHashCode()
  • Copiar y clonar miembrosCopy and Clone members
  • PrintMembers y ToString()PrintMembers and ToString()

Los registros admiten la herencia.Records support inheritance. Puede declarar un nuevo registro derivado de Person como se indica a continuación:You can declare a new record derived from Person as follows:

public record Teacher : Person
{
    public string Subject { get; }

    public Teacher(string first, string last, string sub)
        : base(first, last) => Subject = sub;
}

También puede sellar los registros para evitar la derivación adicional:You can also seal records to prevent further derivation:

public sealed record Student : Person
{
    public int Level { get; }

    public Student(string first, string last, int level) : base(first, last) => Level = level;
}

El compilador sintetiza versiones diferentes de los métodos anteriores.The compiler synthesizes different versions of the methods above. Las signaturas de método dependen de si el tipo de registro está sellado y si la clase base directa es un objeto.The method signatures depend on if the record type is sealed and if the direct base class is object. Los registros deben tener las funciones siguientes:Records should have the following capabilities:

  • La igualdad se basa en valores e incluye una comprobación de coincidencia de los tipos.Equality is value-based, and includes a check that the types match. Por ejemplo, Student no puede ser igual a Person, aunque los dos registros compartan el mismo nombre.For example, a Student can't be equal to a Person, even if the two records share the same name.
  • Los registros tienen una representación de cadena coherente que se genera de forma automática.Records have a consistent string representation generated for you.
  • Los registros admiten la construcción de copias.Records support copy construction. La construcción de copias correctas debe incluir las jerarquías de herencia y las propiedades agregadas por los desarrolladores.Correct copy construction must include inheritance hierarchies, and properties added by developers.
  • Los registros se pueden copiar con modificaciones.Records can be copied with modification. Estas operaciones de copia y modificación admiten la mutación no destructiva.These copy and modify operations supports non-destructive mutation.

Además de las sobrecargas de Equals conocidas, operator == y operator !=, el compilador sintetiza una nueva propiedad EqualityContract.In addition to the familiar Equals overloads, operator ==, and operator !=, the compiler synthesizes a new EqualityContract property. La propiedad devuelve un objeto Type que coincide con el tipo del registro.The property returns a Type object that matches the type of the record. Si el tipo base es object, la propiedad es virtual.If the base type is object, the property is virtual. Si el tipo base es otro tipo de registro, la propiedad es un override.If the base type is another record type, the property is an override. Si el tipo de registro es sealed, la propiedad es sealed.If the record type is sealed, the property is sealed. El método GetHashCode sintetizado usa el valor GetHashCode de todas las propiedades y campos declarados en el tipo base y el tipo de registro.The synthesized GetHashCode uses the GetHashCode from all properties and fields declared in the base type and the record type. Estos métodos sintetizados imponen la igualdad basada en valores en una jerarquía de herencia.These synthesized methods enforce value-based equality throughout an inheritance hierarchy. Esto significa que un registro Student nunca se considerará igual que un registro Person con el mismo nombre.That means a Student will never be considered equal to a Person with the same name. Los tipos de los dos registros deben coincidir y todas las propiedades compartidas entre los tipos de registro deben ser iguales.The types of the two records must match as well as all properties shared among the record types being equal.

Los registros también tienen un constructor sintetizado y un método "clone" para crear copias.Records also have a synthesized constructor and a "clone" method for creating copies. El constructor sintetizado tiene un solo parámetro del tipo de registro.The synthesized constructor has a single parameter of the record type. Genera un nuevo registro con los mismos valores para todas las propiedades del registro.It produces a new record with the same values for all properties of the record. Este constructor es privado si el registro está sellado; de lo contrario está protegido.This constructor is private if the record is sealed, otherwise it's protected. El método "clone" sintetizado admite la construcción de copias para las jerarquías de registros.The synthesized "clone" method supports copy construction for record hierarchies. El término "clone" está entre comillas porque el nombre real es el compilador generado.The term "clone" is in quotes because the actual name is compiler generated. No se puede crear un método denominado Clone en un tipo de registro.You can't create a method named Clone in a record type. El método "clone" sintetizado devuelve el tipo de registro que se copia mediante el envío virtual.The synthesized "clone" method returns the type of record being copied using virtual dispatch. El compilador agrega distintos modificadores para el método "clone", en función de los modificadores de acceso de record:The compiler adds different modifiers for the "clone" method depending on the access modifiers on the record:

  • Si el tipo de registro es abstract, el método "clone" también es abstract.If the record type is abstract, the "clone" method is also abstract. Si el tipo base no es object, el método también es override.If the base type isn't object, the method is also override.
  • Para los tipos de registro que no son abstract cuando el tipo base es object:For record types that aren't abstract when the base type is object:
    • Si el registro es sealed, no se agregan modificadores adicionales al método "clone" (lo que significa que no es virtual).If the record is sealed, no additional modifiers are added to the "clone" method (meaning it is not virtual).
    • Si el registro no es sealed, el método "clone" es virtual.If the record isn't sealed, the "clone" method is virtual.
  • Para los tipos de registro que no son abstract cuando el tipo base no es object:For record types that aren't abstract when the base type is not object:
    • Si el registro es sealed, el método "clone" también es sealed.If the record is sealed, the "clone" method is also sealed.
    • Si el registro no es sealed, el método "clone" es override.If the record isn't sealed, the "clone" method is override.

El resultado de todas estas reglas es que la igualdad se implementa de forma coherente en cualquier jerarquía de tipos de registro.The result of all these rules is the equality is implemented consistently across any hierarchy of record types. Dos registros son iguales entre ellos si sus propiedades son iguales y sus tipos son los mismos, como se muestra en el ejemplo siguiente:Two records are equal to each other if their properties are equal and their types are the same, as shown in the following example:

var person = new Person("Bill", "Wagner");
var student = new Student("Bill", "Wagner", 11);

Console.WriteLine(student == person); // false

El compilador sintetiza dos métodos que admiten la salida impresa: una invalidación de ToString() y PrintMembers.The compiler synthesizes two methods that support printed output: a ToString() override, and PrintMembers. PrintMembers toma System.Text.StringBuilder como argumento.The PrintMembers takes a System.Text.StringBuilder as its argument. Anexa una lista separada por comas de nombres de propiedad y valores para todas las propiedades del tipo de registro.It appends a comma-separated list of property names and values for all properties in the record type. PrintMembers llama a la implementación base de cualquier registro derivado de otros registros.PrintMembers calls the base implementation for any records derived from other records. La invalidación de ToString() devuelve la cadena generada por PrintMembers, incluida entre { y }.The ToString() override returns the string produced by PrintMembers, surrounded by { and }. Por ejemplo, el método ToString() de Student devuelve un objeto string como en el código siguiente:For example, the ToString() method for Student returns a string like the following code:

"Student { LastName = Wagner, FirstName = Bill, Level = 11 }"

En los ejemplos mostrados hasta ahora se usa la sintaxis tradicional para declarar propiedades.The examples shown so far use traditional syntax to declare properties. Hay una forma más concisa denominada registros posicionales.There's a more concise form called positional records. Estos son los tres tipos de registro definidos anteriormente como registros posicionales:Here are the three record types defined earlier as positional records:

public record Person(string FirstName, string LastName);

public record Teacher(string FirstName, string LastName,
    string Subject)
    : Person(FirstName, LastName);

public sealed record Student(string FirstName,
    string LastName, int Level)
    : Person(FirstName, LastName);

Estas declaraciones crean la misma funcionalidad que la versión anterior (con un par de características adicionales que se describen en la sección siguiente).These declarations create the same functionality as the earlier version (with a couple extra features covered in the following section). Estas declaraciones finalizan con un punto y coma en lugar de corchetes, porque estos registros no agregan métodos adicionales.These declarations end with a semicolon instead of brackets because these records don't add additional methods. Puede agregar un cuerpo e incluir también otros métodos adicionales:You can add a body, and include any additional methods as well:

public record Pet(string Name)
{
    public void ShredTheFurniture() =>
        Console.WriteLine("Shredding furniture");
}

public record Dog(string Name) : Pet(Name)
{
    public void WagTail() =>
        Console.WriteLine("It's tail wagging time");

    public override string ToString()
    {
        StringBuilder s = new();
        base.PrintMembers(s);
        return $"{s.ToString()} is a dog";
    }
}

El compilador genera un método Deconstruct para los registros posicionales.The compiler produces a Deconstruct method for positional records. El método Deconstruct tiene parámetros que coinciden con los nombres de todas las propiedades públicas del tipo de registro.The Deconstruct method has parameters that match the names of all public properties in the record type. El método Deconstruct se puede usar para deconstruir el registro en sus propiedades de componente:The Deconstruct method can be used to deconstruct the record into its component properties:

var person = new Person("Bill", "Wagner");

var (first, last) = person;
Console.WriteLine(first);
Console.WriteLine(last);

Por último, los registros admiten expresiones with.Finally, records support with expressions. Una * expresión with_ _ indica al compilador que cree una copia de un registro, pero con propiedades especificadas modificadas:A *with expression_ _ instructs the compiler to create a copy of a record, but _with specified properties modified:

Person brother = person with { FirstName = "Paul" };

La línea anterior crea un registro Person en el que la propiedad LastName es una copia de person y el valor FirstName es "Paul".The above line creates a new Person record where the LastName property is a copy of person, and the FirstName is "Paul". Puede establecer cualquier número de propiedades en una expresión with.You can set any number of properties in a with expression.

El usuario puede escribir cualquiera de los miembros sintetizados excepto el método "clone".Any of the synthesized members except the "clone" method may be written by you. Si un tipo de registro tiene un método que coincide con la signatura de cualquier método sintetizado, el compilador no sintetiza ese método.If a record type has a method that matches the signature of any synthesized method, the compiler doesn't synthesize that method. El ejemplo de registro Dog anterior contiene un método ToString() codificado a mano como ejemplo.The earlier Dog record example contains a hand coded ToString() method as an example.

Establecedores de solo inicializaciónInit only setters

Los *establecedores de solo inicialización proporcionan una sintaxis coherente para inicializar miembros de un objeto.*Init only setters _ provide consistent syntax to initialize members of an object. Los inicializadores de propiedades indican con claridad qué valor establece cada propiedad.Property initializers make it clear which value is setting which property. El inconveniente es que esas propiedades se deben establecer.The downside is that those properties must be settable. A partir de C# 9.0, puede crear descriptores de acceso init en lugar de descriptores de acceso set para propiedades e indizadores.Starting with C# 9.0, you can create init accessors instead of set accessors for properties and indexers. Los autores de la llamada pueden usar la sintaxis de inicializador de propiedad para establecer estos valores en expresiones de creación, pero esas propiedades son de solo lectura una vez que se ha completado la construcción.Callers can use property initializer syntax to set these values in creation expressions, but those properties are readonly once construction has completed. Los establecedores de solo inicialización proporcionan una ventana para cambiar el estado,Init only setters provide a window to change state. que se cierra cuando finaliza la fase de construcción.That window closes when the construction phase ends. La fase de construcción finaliza de forma eficaz después de que se complete toda la inicialización, incluidos los inicializadores de propiedades y las expresiones with.The construction phase effectively ends after all initialization, including property initializers and with-expressions have completed.

Puede declarar los establecedores de solo init en cualquier tipo que escriba.You can declare init only setters in any type you write. Por ejemplo, en la estructura siguiente se define una estructura de observación meteorológica:For example, the following struct defines a weather observation structure:

public struct WeatherObservation
{
    public DateTime RecordedAt { get; init; }
    public decimal TemperatureInCelsius { get; init; }
    public decimal PressureInMillibars { get; init; }

    public override string ToString() =>
        $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
        $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}

Los autores de la llamada pueden usar la sintaxis de inicializador de propiedades para establecer los valores, a la vez que conservan la inmutabilidad:Callers can use property initializer syntax to set the values, while still preserving the immutability:

var now = new WeatherObservation 
{ 
    RecordedAt = DateTime.Now, 
    TemperatureInCelsius = 20, 
    PressureInMillibars = 998.0m 
};

Pero el cambio de una observación después de la inicialización es un error mediante la asignación a una propiedad de solo inicialización fuera de la inicialización:But, changing an observation after initialization is an error by assigning to an init-only property outside of initialization:

// Error! CS8852.
now.TemperatureInCelsius = 18;

Los establecedores de solo inicialización pueden ser útiles para establecer las propiedades de clase base de las clases derivadas.Init only setters can be useful to set base class properties from derived classes. También pueden establecer propiedades derivadas mediante asistentes en una clase base.They can also set derived properties through helpers in a base class. Los registros posicionales declaran propiedades mediante establecedores de solo inicialización.Positional records declare properties using init only setters. Esos establecedores se usan en expresiones with.Those setters are used in with-expressions. Puede declarar establecedores de solo inicialización para cualquier objeto class o struct que defina.You can declare init only setters for any class or struct you define.

Instrucciones de nivel superiorTop-level statements

Las instrucciones de nivel superior quitan complejidad innecesaria de muchas aplicaciones.Top-level statements remove unnecessary ceremony from many applications. Considere el famoso programaConsider the canonical "Hello World!" "Hola mundo":program:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Solo hay una línea de código que haga algo.There’s only one line of code that does anything. Con las instrucciones de nivel superior, puede reemplazar todo lo que sea reutilizable por la instrucción using y la línea única que realiza el trabajo:With top-level statements, you can replace all that boilerplate with the using statement and the single line that does the work:

using System;

Console.WriteLine("Hello World!");

Si quisiera un programa de una línea, podría quitar la directiva using y usar el nombre de tipo completo:If you wanted a one-line program, you could remove the using directive and use the fully qualified type name:

System.Console.WriteLine("Hello World!");

Solo un archivo de la aplicación puede usar instrucciones de nivel superior.Only one file in your application may use top-level statements. Si el compilador encuentra instrucciones de nivel superior en varios archivos de código fuente, se trata de un error.If the compiler finds top-level statements in multiple source files, it’s an error. También es un error si combina instrucciones de nivel superior con un método de punto de entrada de programa declarado, normalmente Main.It’s also an error if you combine top-level statements with a declared program entry point method, typically a Main method. En cierto sentido, puede pensar que un archivo contiene las instrucciones que normalmente se encontrarían en el método Main de una clase Program.In a sense, you can think that one file contains the statements that would normally be in the Main method of a Program class.

Uno de los usos más comunes de esta característica es la creación de materiales educativos.One of the most common uses for this feature is creating teaching materials. Los desarrolladores principiantes de C# pueden escribir el programa "Hola mundo"Beginner C# developers can write the canonical “Hello World!” en una o dos líneas de código.in one or two lines of code. No se necesitan pasos adicionales.None of the extra ceremony is needed. Pero los desarrolladores veteranos también encontrarán muchas aplicaciones a esta característica.However, seasoned developers will find many uses for this feature as well. Las instrucciones de nivel superior permiten una experiencia de experimentación de tipo script similar a la que proporcionan los cuadernos de Jupyter Notebook.Top-level statements enable a script-like experience for experimentation similar to what Jupyter notebooks provide. Las instrucciones de nivel superior son excelentes para programas y utilidades de consola pequeños.Top-level statements are great for small console programs and utilities. Azure Functions es un caso de uso ideal para las instrucciones de nivel superior.Azure Functions are an ideal use case for top-level statements.

Y sobre todo, las instrucciones de nivel superior no limitan el ámbito ni la complejidad de la aplicación.Most importantly, top-level statements don't limit your application’s scope or complexity. Estas instrucciones pueden acceder a cualquier clase de .NET o usarla.Those statements can access or use any .NET class. Tampoco limitan el uso de argumentos de línea de comandos ni de valores devueltos.They also don’t limit your use of command-line arguments or return values. Las instrucciones de nivel superior pueden acceder a una matriz de cadenas denominada args.Top-level statements can access an array of strings named args. Si las instrucciones de nivel superior devuelven un valor entero, ese valor se convierte en el código devuelto entero de un método Main sintetizado.If the top-level statements return an integer value, that value becomes the integer return code from a synthesized Main method. Las instrucciones de nivel superior pueden contener expresiones asincrónicas.The top-level statements may contain async expressions. En ese caso, el punto de entrada sintetizado devuelve un objeto Task, o Task<int>.In that case, the synthesized entry point returns a Task, or Task<int>.

Mejoras de coincidencia de patronesPattern matching enhancements

C# 9 incluye nuevas mejoras de coincidencia de patrones:C# 9 includes new pattern matching improvements:

  • Los patrones de tipo comprueban si una variable es un tipoType patterns match a variable is a type
  • Los patrones entre paréntesis aplican o resaltan la prioridad de las combinaciones de patronesParenthesized patterns enforce or emphasize the precedence of pattern combinations
  • En los patrones and conjuntivos es necesario que los dos patrones coincidanConjunctive and patterns require both patterns to match
  • En los patrones or disyuntivos es necesario que alguno de los dos patrones coincidaDisjunctive or patterns require either pattern to match
  • En los patrones not negados es necesario que un patrón no coincidaNegated not patterns require that a pattern doesn’t match
  • Los patrones relacionales requieren que la entrada sea menor que, mayor que, menor o igual que, o mayor o igual que una constante determinada.Relational patterns require the input be less than, greater than, less than or equal, or greater than or equal to a given constant.

Estos patrones enriquecen la sintaxis de los patrones.These patterns enrich the syntax for patterns. Tenga en cuenta estos ejemplos:Consider these examples:

public static bool IsLetter(this char c) =>
    c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Como alternativa, con paréntesis opcionales para que quede claro que and tiene mayor precedencia que or:Alternatively, with optional parentheses to make it clear that and has higher precedence than or:

public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

Uno de los usos más comunes es una nueva sintaxis para una comprobación NULL:One of the most common uses is a new syntax for a null check:

if (e is not null)
{
    // ...
}

Cualquiera de estos patrones se puede usar en cualquier contexto en el que se permitan patrones: expresiones de patrón is, expresiones switch, modelos anidados y el patrón de la etiqueta case de una instrucción switch.Any of these patterns can be used in any context where patterns are allowed: is pattern expressions, switch expressions, nested patterns, and the pattern of a switch statement’s case label.

Rendimiento e interoperabilidadPerformance and interop

Tres nuevas características mejoran la compatibilidad con la interoperabilidad nativa y las bibliotecas de bajo nivel que requieren alto rendimiento: enteros de tamaño nativo, punteros de función y la omisión de la marca localsinit.Three new features improve support for native interop and low-level libraries that require high performance: native sized integers, function pointers, and omitting the localsinit flag.

Los enteros de tamaño nativo, nint y nuint, son tipos enteros.Native sized integers, nint and nuint, are integer types. Se expresan mediante los tipos subyacentes System.IntPtr y System.UIntPtr.They're expressed by the underlying types System.IntPtr and System.UIntPtr. El compilador muestra las conversiones y operaciones adicionales para estos tipos como enteros nativos.The compiler surfaces additional conversions and operations for these types as native ints. Los enteros con tamaño nativo definen propiedades para MaxValue o MinValue.Native sized integers define properties for MaxValue or MinValue. Estos valores no se pueden expresar como constantes en tiempo de compilación porque dependen del tamaño nativo de un entero en el equipo de destino.These values can't be expressed as compile time constants because they depend on the native size of an integer on the target machine. Estos valores son de solo lectura en el entorno de ejecución.Those values are readonly at runtime. Puede usar valores constantes para nint en el intervalo [int.MinValue ..You can use constant values for nint in the range [int.MinValue .. int.MaxValue].int.MaxValue]. Puede usar valores constantes para nuint en el intervalo [uint.MinValue ..You can use constant values for nuint in the range [uint.MinValue .. uint.MaxValue].uint.MaxValue]. El compilador realiza un plegamiento constante para todos los operadores unarios y binarios que usan los tipos System.Int32 y System.UInt32.The compiler performs constant folding for all unary and binary operators using the System.Int32 and System.UInt32 types. Si el resultado no cabe en 32 bits, la operación se ejecuta en tiempo de ejecución y no se considera una constante.If the result doesn't fit in 32 bits, the operation is executed at runtime and isn't considered a constant. Los enteros con tamaño nativo pueden aumentar el rendimiento en escenarios en los que se usa la aritmética de enteros y es necesario tener el rendimiento más rápido posible.Native sized integers can increase performance in scenarios where integer math is used extensively and needs to have the fastest performance possible.

Los punteros de función proporcionan una sintaxis sencilla para acceder a los códigos de operación de lenguaje intermedio ldftn y calli.Function pointers provide an easy syntax to access the IL opcodes ldftn and calli. Puede declarar punteros de función con la nueva sintaxis de delegate_.You can declare function pointers using new delegate_ syntax. Un tipo delegate* es un tipo de puntero.A delegate* type is a pointer type. Al invocar el tipo delegate* se usa calli, a diferencia de un delegado que usa callvirt en el método Invoke().Invoking the delegate* type uses calli, in contrast to a delegate that uses callvirt on the Invoke() method. Sintácticamente, las invocaciones son idénticas.Syntactically, the invocations are identical. La invocación del puntero de función usa la convención de llamada managed.Function pointer invocation uses the managed calling convention. Agregue la palabra clave unmanaged después de la sintaxis de delegate* para declarar que quiere la convención de llamada unmanaged.You add the unmanaged keyword after the delegate* syntax to declare that you want the unmanaged calling convention. Se pueden especificar otras convenciones de llamada mediante atributos en la declaración de delegate*.Other calling conventions can be specified using attributes on the delegate* declaration.

Por último, puede agregar System.Runtime.CompilerServices.SkipLocalsInitAttribute para indicar al compilador que no emita la marca localsinit.Finally, you can add the System.Runtime.CompilerServices.SkipLocalsInitAttribute to instruct the compiler not to emit the localsinit flag. Esta marca indica al CLR que inicialice en cero todas las variables locales.This flag instructs the CLR to zero-initialize all local variables. La marca localsinit ha sido el comportamiento predeterminado en C# desde la versión 1.0.The localsinit flag has been the default behavior for C# since 1.0. Pero la inicialización en cero adicional puede afectar al rendimiento en algunos escenarios.However, the extra zero-initialization may have measurable performance impact in some scenarios. En concreto, cuando se usa stackalloc.In particular, when you use stackalloc. En esos casos, puede agregar SkipLocalsInitAttribute.In those cases, you can add the SkipLocalsInitAttribute. Puede agregarlo a un único método o propiedad, a un objeto class, struct, interface, o incluso a un módulo.You may add it to a single method or property, or to a class, struct, interface, or even a module. Este atributo no afecta a los métodos abstract; afecta al código generado para la implementación.This attribute doesn't affect abstract methods; it affects the code generated for the implementation.

Estas características pueden aumentar significativamente el rendimiento en algunos escenarios.These features can improve performance in some scenarios. Solo se deben usar después de realizar pruebas comparativas minuciosamente antes y después de la adopción.They should be used only after careful benchmarking both before and after adoption. El código que implica enteros con tamaño nativo se debe probar en varias plataformas de destino con distintos tamaños de enteros.Code involving native sized integers must be tested on multiple target platforms with different integer sizes. Las demás características requieren código no seguro.The other features require unsafe code.

Características de ajuste y finalizaciónFit and finish features

Muchas de las características restantes ayudan a escribir código de forma más eficaz.Many of the other features help you write code more efficiently. En C# 9.0, puede omitir el tipo de una expresión new cuando ya se conoce el tipo del objeto creado.In C# 9.0, you can omit the type in a new expression when the created object's type is already known. El uso más común es en las declaraciones de campo:The most common use is in field declarations:

private List<WeatherObservation> _observations = new();

El tipo de destino new también se puede usar cuando es necesario crear un objeto para pasarlo como argumento a un método.Target-typed new can also be used when you need to create a new object to pass as an argument to a method. Considere un método ForecastFor() con la signatura siguiente:Consider a ForecastFor() method with the following signature:

public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)

Podría llamarlo de esta forma:You could call it as follows:

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

Otra aplicación muy útil de esta característica es para combinarla con propiedades de solo inicialización para inicializar un objeto nuevo:Another nice use for this feature is to combine it with init only properties to initialize a new object:

WeatherStation station = new() { Location = "Seattle, WA" };

Puede devolver una instancia creada por el constructor predeterminado mediante una declaración return new();.You can return an instance created by the default constructor using a return new(); statement.

Una característica similar mejora la resolución de tipos de destino de las expresiones condicionales.A similar feature improves the target type resolution of conditional expressions. Con este cambio, las dos expresiones no necesitan tener una conversión implícita de una a otra, pero pueden tener conversiones implícitas a un tipo de destino.With this change, the two expressions need not have an implicit conversion from one to the other, but may both have implicit conversions to a target type. Lo más probable es que no note este cambio.You likely won’t notice this change. Lo que observará es que ahora funcionan algunas expresiones condicionales para las que anteriormente se necesitan conversiones o que no se compilaban.What you will notice is that some conditional expressions that previously required casts or wouldn’t compile now just work.

A partir de C# 9.0, puede agregar el modificador static a expresiones lambda o métodos anónimos.Starting in C# 9.0, you can add the static modifier to lambda expressions or anonymous methods. Las expresiones lambda estáticas son análogas a las funciones static locales: un método anónimo o una expresión lambda estáticos no puede capturar variables locales ni el estado de la instancia.Static lambda expressions are analogous to the static local functions: a static lambda or anonymous method can't capture local variables or instance state. El modificador static impide la captura accidental de otras variables.The static modifier prevents accidentally capturing other variables.

Los tipos de valor devuelto covariantes proporcionan flexibilidad a los tipos de valor devuelto de los métodos override.Covariant return types provide flexibility for the return types of override methods. Un método override puede devolver un tipo derivado del tipo de valor devuelto del método base invalidado.An override method can return a type derived from the return type of the overridden base method. Esto puede ser útil para los registros y para otros tipos que admiten métodos de generador o clonación virtuales.This can be useful for records and for other types that support virtual clone or factory methods.

Además, el bucle foreach reconocerá y usará un método de extensión GetEnumerator que, de otro modo, satisface el patrón foreach.In addition, the foreach loop will recognize and use an extension method GetEnumerator that otherwise satisfies the foreach pattern. Este cambio significa que foreach es coherente con otras construcciones basadas en patrones, como el patrón asincrónico y la desconstrucción basada en patrones.This change means foreach is consistent with other pattern-based constructions such as the async pattern, and pattern-based deconstruction. En la práctica, esto quiere decir que puede agregar compatibilidad con foreach a cualquier tipo.In practice, this change means you can add foreach support to any type. Debe limitar su uso a cuando la enumeración de un objeto tiene sentido en el diseño.You should limit its use to when enumerating an object makes sense in your design.

Después, puede usar descartes como parámetros para las expresiones lambda.Next, you can use discards as parameters to lambda expressions. De esta forma no tiene que asignar un nombre al argumento y el compilador puede evitar usarlo.This convenience enables you to avoid naming the argument, and the compiler may avoid using it. Use _ para cualquier argumento.You use the _ for any argument. Para más información, consulte sección sobre parámetros de entrada de una expresión lambda en el artículo sobre expresiones lambda.For more information, see the Input parameters of a lambda expression section of the Lambda expressions article.

Por último, ahora puede aplicar atributos a las funciones locales.Finally, you can now apply attributes to local functions. Por ejemplo, puede aplicar anotaciones de atributo que admiten un valor NULL a las funciones locales.For example, you can apply nullable attribute annotations to local functions.

Compatibilidad con generadores de códigoSupport for code generators

Dos últimas características admiten generadores de código de C#.Two final features support C# code generators. Los generadores de código de C# son un componente que se puede escribir y que es similar a una corrección de código o un analizador Roslyn.C# code generators are a component you can write that is similar to a roslyn analyzer or code fix. La diferencia es que los generadores de código analizan el código y escriben nuevos archivos de código fuente como parte del proceso de compilación.The difference is that code generators analyze code and write new source code files as part of the compilation process. Un generador de código típico busca atributos y otras convenciones en el código.A typical code generator searches code for attributes or other conventions.

Un generador de código lee atributos u otros elementos de código mediante las API de análisis de Roslyn.A code generator reads attributes or other code elements using the Roslyn analysis APIs. A partir de esa información, agrega código nuevo a la compilación.From that information, it adds new code to the compilation. Los generadores de código fuente solo pueden agregar código; no se les permite modificar ningún código existente en la compilación.Source generators can only add code; they aren't allowed to modify any existing code in the compilation.

Las dos características agregadas a los generadores de código son las extensiones de la sintaxis de métodos parciales y los inicializadores de módulos.The two features added for code generators are extensions to partial method syntax _, and _module initializers*_. En primer lugar, los cambios en los métodos parciales.First, the changes to partial methods. Antes de C# 9.0, los métodos parciales eran private, pero no podían especificar un modificador de acceso, tener un valor devuelto void ni parámetros out.Before C# 9.0, partial methods are private but can't specify an access modifier, have a void return, and can't have out parameters. Estas restricciones implican que si no se proporciona ninguna implementación de método, el compilador quita todas las llamadas al método parcial.These restrictions meant that if no method implementation is provided, the compiler removes all calls to the partial method. En C# 9.0 se quitan estas restricciones, pero es necesario que las declaraciones de métodos parciales tengan una implementación.C# 9.0 removes these restrictions, but requires that partial method declarations have an implementation. Los generadores de código pueden proporcionar esa implementación.Code generators can provide that implementation. Para evitar la introducción de un cambio importante, el compilador tiene en cuenta cualquier método parcial sin un modificador de acceso para seguir las reglas anteriores.To avoid introducing a breaking change, the compiler considers any partial method without an access modifier to follow the old rules. Si el método parcial incluye el modificador de acceso private, las nuevas reglas rigen ese método parcial.If the partial method includes the private access modifier, the new rules govern that partial method.

La segunda característica nueva de los generadores de código son los inicializadores de módulos.The second new feature for code generators is _*module initializers**. Los inicializadores de módulos son métodos que tienen asociado el atributo ModuleInitializerAttribute.Module initializers are methods that have the ModuleInitializerAttribute attribute attached to them. El entorno de ejecución llamará a estos métodos antes de cualquier otro acceso de campo o invocación de método en todo el módulo.These methods will be called by the runtime before any other field access or method invocation within the entire module. Un método de inicializador de módulo:A module initializer method:

  • Debe ser estáticoMust be static
  • No debe tener parámetrosMust be parameterless
  • Debe devolver voidMust return void
  • No debe ser un método genéricoMust not be a generic method
  • No debe estar incluido en una clase genéricaMust not be contained in a generic class
  • Debe ser accesible desde el módulo contenedorMust be accessible from the containing module

Ese último punto significa en realidad que el método y su clase contenedora deben ser internos o públicos.That last bullet point effectively means the method and its containing class must be internal or public. El método no puede ser una función local.The method can't be a local function.