Novedades del entorno de ejecución de .NET 8
En este artículo se describen las nuevas características del entorno de ejecución de .NET para .NET 8.
Mejoras de rendimiento
.NET 8 incluye mejoras en la generación de código y la compilación Just-In-Time (JIT):
- Mejoras de rendimiento de Arm64
- Mejoras de SIMD
- Compatibilidad con extensiones DE ISA de AVX-512 (consulte Vector512 y AVX-512)
- Mejoras nativas de la nube
- Mejoras en el rendimiento de procesamiento JIT
- Optimizaciones generales y de bucle
- Acceso optimizado para los campos marcados con ThreadStaticAttribute
- Asignación consecutiva de registros. Arm64 tiene dos instrucciones para la búsqueda de vectores de tabla, que requieren que todas las entidades de sus operandos de tupla estén presentes en registros consecutivos.
- JIT/NativeAOT ahora puede expandir y vectorizar automáticamente algunas operaciones de memoria con SIMD, como comparación, copia y puesta a cero, si puede determinar sus tamaños en tiempo de compilación.
Además, se ha mejorado la optimización dinámica guiada por perfiles (PGO) y ahora está habilitada de forma predeterminada. Ya no es necesario usar una opción de configuración del entorno de ejecución de para habilitarla. La PGO dinámica funciona de la mano con la compilación en niveles para optimizar aún más el código en función de la instrumentación adicional que se pone en marcha durante el nivel 0.
De media, la PGO dinámica aumenta el rendimiento en un 15 %. En un conjunto de referencia de aproximadamente 4600 pruebas, un 23 % constató mejoras de rendimiento del 20 % o más.
Promoción de estructura de Codegen
.NET 8 incluye un nuevo pase de optimización de promoción física para codegen que generaliza la capacidad de JIT para promover variables de estructura. Esta optimización (también denominada reemplazo escalar de agregados) reemplaza los campos de variables de estructura por variables primitivas que el JIT puede razonar y optimizar con mayor precisión.
El JIT ya admite esta optimización, pero con varias limitaciones grandes, entre las que se incluyen:
- Solo se admite para estructuras con cuatro o menos campos.
- Solo se admitía si cada campo era un tipo primitivo o una estructura simple que encapsulaba un tipo primitivo.
La promoción física elimina estas limitaciones, lo que corrige una serie de problemas JIT de larga duración.
Recolección de basura
.NET 8 agrega una funcionalidad para ajustar el límite de memoria sobre la marcha. Esto es útil en escenarios de servicio en la nube, donde la demanda llega y va. Para ser rentable, los servicios deben ajustar su consumo de recursos al alza y a la baja a medida que fluctúa la demanda. Cuando un servicio detecta una disminución de la demanda, puede reducir verticalmente el consumo de recursos reduciendo su límite de memoria. Anteriormente, esto produciría un error porque el recolector de elementos no utilizados (GC) no era consciente del cambio y podría asignar más memoria que el nuevo límite. Con este cambio, puede llamar a la API RefreshMemoryLimit() para actualizar el GC con el nuevo límite de memoria.
Hay algunas limitaciones que debe tener en cuenta:
- En plataformas de 32 bits (por ejemplo, Windows x86 y Linux ARM), .NET no puede establecer un nuevo límite máximo de montón si aún no hay uno.
- La API podría devolver un código de estado distinto de cero que indica el error de actualización. Esto puede ocurrir si la reducción es demasiado agresiva y no deja espacio para que el GC maniobre. En este caso, considere la posibilidad de llamar a
GC.Collect(2, GCCollectionMode.Aggressive)
para reducir el uso de memoria actual e inténtelo de nuevo. - Si aumenta el límite de memoria más allá del tamaño que el GC cree que el proceso puede manejar al inicio, la llamada a
RefreshMemoryLimit
tendrá éxito, pero no podrá usar más memoria de la que percibe como límite.
El siguiente fragmento de código muestra cómo llamar a la API.
GC.RefreshMemoryLimit();
También puede actualizar algunas de las opciones de configuración de GC relacionadas con el límite de memoria. El fragmento de código siguiente establece el límite estricto del heap en 100 mebibytes (MiB):
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
La API puede producir un InvalidOperationException si el límite máximo no es válido, por ejemplo, en el caso de porcentajes de límite máximo de montón negativos y si el límite máximo es demasiado bajo. Esto puede ocurrir si el límite máximo del montón que establecerá la actualización, ya sea debido a la nueva configuración de AppData o implícita por los cambios de límite de memoria del contenedor, es menor que lo que ya se ha confirmado.
Globalización para aplicaciones móviles
Las aplicaciones móviles en iOS, tvOS y MacCatalyst pueden optar por un nuevo modo de globalización híbrido que utiliza un paquete ICU más ligero. En el modo híbrido, los datos de globalización se extraen parcialmente de la agrupación de ICU y parcialmente de las llamadas a las API nativas. El modo híbrido sirve para todas las configuraciones regionales compatibles con móviles.
El modo híbrido es más adecuado para las aplicaciones que no pueden funcionar en el modo de globalización invariable y que usan referencias culturales recortadas de datos de ICU en dispositivos móviles. También puede usarlo cuando quiera cargar un archivo de datos de ICU más pequeño. (El archivo icudt_hybrid.dat es 34,5 % menor que el archivo de datos de ICU predeterminado icudt.dat).
Para usar el modo de globalización híbrida, establezca la propiedad HybridGlobalization
MSBuild en true:
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Hay algunas limitaciones que debe tener en cuenta:
- Debido a las limitaciones de la API nativa, no todas las API de globalización se admiten en modo híbrido.
- Algunas de las API admitidas tienen un comportamiento diferente.
Para comprobar si su aplicación se ve afectada, consulte Diferencias de comportamiento.
Interoperabilidad COM generada por el origen
.NET 8 incluye un nuevo generador de origen que admite la interoperación con interfaces COM. Puede usar el GeneratedComInterfaceAttribute para marcar una interfaz como una interfaz COM para el generador de origen. A continuación, el generador de código fuente generará código para habilitar la llamada desde código de C# al código no administrado. También genera código para habilitar la llamada desde código no administrado en C#. Este generador de origen se integra con LibraryImportAttribute y puede usar tipos con GeneratedComInterfaceAttribute como parámetros y tipos devueltos en métodos con atributos LibraryImport
.
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
void DoWork();
}
internal partial class MyNativeLib
{
[LibraryImport(nameof(MyNativeLib))]
public static partial void GetComInterface(out IComInterface comInterface);
}
El generador de origen también admite el nuevo atributo GeneratedComClassAttribute para permitirle pasar tipos que implementan interfaces con el atributo GeneratedComInterfaceAttribute al código no administrado. El generador de origen generará el código necesario para exponer un objeto COM que implemente las interfaces y reenvíe las llamadas a la implementación administrada.
Los métodos en interfaces con el atributo GeneratedComInterfaceAttribute admiten todos los mismos tipos que LibraryImportAttribute
y ahora LibraryImportAttribute
admiten tipos con atributos GeneratedComInterface
y tipos con atributos GeneratedComClass
.
Si el código de C# solo usa una interfaz con atributos de GeneratedComInterface
para encapsular un objeto COM desde código no administrado o encapsular un objeto administrado de C# para exponerlo a código no administrado, puede usar las opciones de la propiedad Options para personalizar qué código se generará. Estas opciones significan que no es necesario escribir serializadores que sepa que no se usarán para los escenarios.
El generador de origen usa el nuevo tipo de StrategyBasedComWrappers para crear y administrar los contenedores de objetos COM y los contenedores de objetos administrados. Este nuevo tipo controla la experiencia de usuario de .NET esperada para la interoperabilidad COM, al tiempo que proporciona puntos de personalización para usuarios avanzados. Si su aplicación tiene su propio mecanismo para definir tipos de COM o si necesita admitir escenarios que COM generados automáticamente no admite actualmente, considere usar el nuevo tipo StrategyBasedComWrappers para agregar las características que faltan para su escenario y obtener la misma experiencia de usuario en .NET para los tipos COM.
Si usa Visual Studio, los nuevos analizadores y correcciones de código facilitan la conversión del código de interoperabilidad COM existente para usar la interoperabilidad generada por el origen. Junto a cada interfaz que tiene ComImportAttribute, una bombilla ofrece una opción para convertir a la interoperabilidad generada por el origen. La corrección cambia la interfaz para usar el atributo GeneratedComInterfaceAttribute. Y junto a todas las clases que implementan una interfaz con GeneratedComInterfaceAttribute
, una bombilla ofrece una opción para agregar el atributo GeneratedComClassAttribute al tipo. Una vez convertidos los tipos, puede mover los métodos DllImport
para usar LibraryImportAttribute
.
Limitaciones
El generador de origen COM no admite la afinidad de apartamento, con la palabra clave new
para activar una coclase COM y las SIGUIENTES API:
- Interfaces basadas en IDispatch.
- Interfaces basadas en IInspectable.
- Propiedades y eventos COM.
Generador de origen de enlace de configuración
.NET 8 presenta un generador de origen para proporcionar una configuración de AOT y fácil de recortar en ASP.NET Core. El generador es una alternativa a la implementación basada en reflexión preexistente.
El generador de origen sondea las llamadas Configure(TOptions), Bind y Get, desde las cuales recuperar información de tipo. Cuando el generador está habilitado en un proyecto, el compilador elige implícitamente los métodos generados sobre las implementaciones preexistentes del framework basado en reflexión.
No se necesitan cambios en el código fuente para usar el generador. Está habilitado de forma predeterminada en aplicaciones web compiladas por AOT y, cuando PublishTrimmed
está establecido en true
(aplicaciones de .NET 8+). Para otros tipos de proyecto, el generador de origen está desactivado de forma predeterminada, pero puede optar por establecer la propiedad EnableConfigurationBindingGenerator
en true
en el archivo de proyecto:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
En el código siguiente, se muestra un ejemplo de cómo invocar al enlazador.
public class ConfigBindingSG
{
static void RunIt(params string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfigurationSection section = builder.Configuration.GetSection("MyOptions");
// !! Configure call - to be replaced with source-gen'd implementation
builder.Services.Configure<MyOptions>(section);
// !! Get call - to be replaced with source-gen'd implementation
MyOptions? options0 = section.Get<MyOptions>();
// !! Bind call - to be replaced with source-gen'd implementation
MyOptions options1 = new();
section.Bind(options1);
WebApplication app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
}
public class MyOptions
{
public int A { get; set; }
public string S { get; set; }
public byte[] Data { get; set; }
public Dictionary<string, string> Values { get; set; }
public List<MyClass> Values2 { get; set; }
}
public class MyClass
{
public int SomethingElse { get; set; }
}
}
Bibliotecas de .NET principales
Esta sección contiene los subtemas siguientes:
- Reflexión
- Serialización
- Abstracción de tiempo
- mejoras en UTF8
- Métodos para trabajar con aleatoriedad
- tipos centrados en el rendimiento
- System.Numerics and System.Runtime.Intrinsics
- Validación de datos
- Métricas
- Criptografía
- Redes
- Métodos ZipFile basados en secuencias
Reflexión
Los punteros de función se introdujeron en .NET 5, pero la compatibilidad correspondiente con la reflexión no se agregó en ese momento. Cuando se usa typeof
o la reflexión en un puntero de función, por ejemplo, typeof(delegate*<void>())
o FieldInfo.FieldType
respectivamente, se devuelve un objeto IntPtr. A partir de .NET 8, se devuelve un objeto System.Type en su lugar. Este tipo proporciona acceso a los metadatos del puntero de función, incluidas las convenciones de llamada, el tipo de valor devuelto y los parámetros.
Nota
Una instancia de puntero de función, que es una dirección física de una función, continúa representándose como IntPtr. Solo ha cambiado el tipo de reflexión.
La nueva funcionalidad solo se implementa actualmente en el entorno de ejecución de CoreCLR y MetadataLoadContext.
Se han agregado nuevas API a System.Type, como IsFunctionPointer, y a System.Reflection.PropertyInfo, System.Reflection.FieldInfoy System.Reflection.ParameterInfo. En el código siguiente se muestra cómo usar algunas de las nuevas API para la reflexión.
using System;
using System.Reflection;
// Sample class that contains a function pointer field.
public unsafe class UClass
{
public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}
internal class FunctionPointerReflection
{
public static void RunIt()
{
FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));
// Obtain the function pointer type from a field.
Type? fpType = fieldInfo?.FieldType;
// New methods to determine if a type is a function pointer.
Console.WriteLine(
$"IsFunctionPointer: {fpType?.IsFunctionPointer}");
Console.WriteLine(
$"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");
// New methods to obtain the return and parameter types.
Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");
if (fpType is not null)
{
foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
{
Console.WriteLine($"Parameter type: {parameterType}");
}
}
// Access to custom modifiers and calling conventions requires a "modified type".
Type? modifiedType = fieldInfo?.GetModifiedFieldType();
// A modified type forwards most members to its underlying type.
Type? normalType = modifiedType?.UnderlyingSystemType;
if (modifiedType is not null)
{
// New method to obtain the calling conventions.
foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
{
Console.WriteLine($"Calling convention: {callConv}");
}
}
// New method to obtain the custom modifiers.
Type[]? modifiers =
modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();
if (modifiers is not null)
{
foreach (Type modreq in modifiers)
{
Console.WriteLine($"Required modifier for first parameter: {modreq}");
}
}
}
}
En el ejemplo anterior se genera la siguiente salida:
IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute
Serialización
Se han realizado muchas mejoras en la funcionalidad de serialización y deserialización de System.Text.Json en .NET 8. Por ejemplo, puede personalizar el manejo de las propiedades JSON que no están en el POCO.
En las secciones siguientes se describen otras mejoras de serialización:
- compatibilidad integrada con tipos adicionales
- Generador de origen
- jerarquías de interfaz
- Directivas de nomenclatura
- Propiedades de solo lectura
- Deshabilitar la opción predeterminada basada en reflexión
- nuevos métodos de API de JsonNode
- Miembros no públicos
- API de deserialización de streaming
- Método de extensión WithAddedModifier
- Nuevas sobrecargas JsonContent.Create
- Congelar una instancia de JsonSerializerOptions
Para obtener más información sobre la serialización JSON en general, consulte serialización y deserialización de JSON en .NET.
Compatibilidad integrada con tipos adicionales
El serializador tiene compatibilidad integrada con los siguientes tipos adicionales.
Half, Int128y UInt128 tipos numéricos.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Valores Memory<T> y ReadOnlyMemory<T>. Los valores
byte
se serializan en cadenas Base64 y otros tipos en matrices JSON.JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID" JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
Generador de código fuente
En .NET 8 se incluyen mejoras del generador de código fuente System.Text.Json destinadas a equiparar la experiencia de AOT nativa con el serializador basado en reflexión. Por ejemplo:
El generador de origen ahora admite la serialización de tipos con propiedades
required
yinit
. Ambas propiedades ya se admitían en la serialización basada en reflexión.Se ha mejorado el formato del código generado por el código fuente.
JsonSourceGenerationOptionsAttribute tiene paridad de características con JsonSerializerOptions. Para obtener más información, vea Especificar opciones (generación de origen).
Diagnósticos adicionales (como SYSLIB1034 y SYSLIB1039).
No incluya tipos de propiedades ignoradas o inaccesibles.
Compatibilidad con declaraciones de anidamiento
JsonSerializerContext
dentro de tipos arbitrarios.Agrega compatibilidad con los tipos generados por el compilador o indescriptibles en escenarios de generación de origen débilmente tipados. Dado que el generador de código fuente no puede especificar explícitamente los tipos generados por el compilador, System.Text.Json ahora realiza la resolución del ancestro más cercano en tiempo de ejecución. Esta resolución determina el supertipo más adecuado con el que serializar el valor.
Nuevo tipo de convertidor
JsonStringEnumConverter<TEnum>
. La clase JsonStringEnumConverter existente no se admite en AOT nativo. Puede anotar los tipos de enumeración de la siguiente manera:[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))] public enum MyEnum { Value1, Value2, Value3 } [JsonSerializable(typeof(MyEnum))] public partial class MyContext : JsonSerializerContext { }
Para obtener más información, consulte Serialización de campos de enumeración como cadenas.
La nueva propiedad
JsonConverter.Type
permite buscar el tipo de una instancia deJsonConverter
no genérica:Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
La propiedad acepta valores NULL, ya que devuelve
null
para las instanciasJsonConverterFactory
ytypeof(T)
para las instanciasJsonConverter<T>
.
Generadores de fuentes en cadena
La clase JsonSerializerOptions incluye una nueva propiedad TypeInfoResolverChain que complementa la propiedad TypeInfoResolver existente. Estas propiedades se usan en la personalización del contrato para encadenar generadores de origen. La adición de la nueva propiedad significa que no es necesario especificar todos los componentes encadenados en un sitio de llamada; se pueden agregar después del hecho. TypeInfoResolverChain también le permite introspectar la cadena o quitar componentes de ella. Para obtener más información, consulte Combinar generadores de código fuente.
Además, ahora JsonSerializerOptions.AddContext<TContext>() está obsoleto. Se ha reemplazado por las propiedades TypeInfoResolver y TypeInfoResolverChain. Para obtener más información, vea SYSLIB0049.
Jerarquías de interfaz
.NET 8 agrega compatibilidad para serializar propiedades de jerarquías de interfaz.
En el código siguiente se muestra un ejemplo en el que las propiedades de la interfaz implementada inmediatamente y su interfaz base se serializan.
public static void InterfaceHierarchies()
{
IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
string json = JsonSerializer.Serialize(value);
Console.WriteLine(json); // {"Derived":1,"Base":0}
}
public interface IBase
{
public int Base { get; set; }
}
public interface IDerived : IBase
{
public int Derived { get; set; }
}
public class DerivedImplement : IDerived
{
public int Base { get; set; }
public int Derived { get; set; }
}
Directivas de nomenclatura
JsonNamingPolicy
incluye nuevas directivas de nomenclatura para las conversiones de nombres de propiedad snake_case
(con guion bajo) y kebab-case
(con guion). Use estas directivas de forma similar a la directiva de JsonNamingPolicy.CamelCase existente:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Para obtener más información, consulte Uso de una directiva de nomenclatura integrada.
Propiedades de solo lectura
Ahora puede deserializar en campos o propiedades de solo lectura (es decir, aquellos que no tienen un descriptor de acceso set
).
Para optar por este soporte globalmente, configure una nueva opción, PreferredObjectCreationHandling, a JsonObjectCreationHandling.Populate. Si la compatibilidad es un problema, también puede habilitar la funcionalidad de forma más granular colocando el atributo [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
en tipos específicos cuyas propiedades se van a rellenar o en propiedades individuales.
Por ejemplo, considere el siguiente código que al deserializar crea un tipo CustomerInfo
con dos propiedades de solo lectura.
public static void ReadOnlyProperties()
{
CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
{ "Names":["John Doe"], "Company":{"Name":"Contoso"} }
""")!;
Console.WriteLine(JsonSerializer.Serialize(customer));
}
class CompanyInfo
{
public required string Name { get; set; }
public string? PhoneNumber { get; set; }
}
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
// Both of these properties are read-only.
public List<string> Names { get; } = new();
public CompanyInfo Company { get; } = new()
{
Name = "N/A",
PhoneNumber = "N/A"
};
}
Antes de .NET 8, los valores de entrada se omitían y las propiedades Names
y Company
conservaban sus valores predeterminados.
{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}
Ahora, los valores de entrada se usan para rellenar las propiedades que son de solo lectura durante la deserialización.
{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}
Para obtener más información sobre el rellenar comportamiento de deserialización, vea Rellenar propiedades inicializadas.
Deshabilitación del valor predeterminado basado en reflexión
Ahora puede deshabilitar el uso del serializador basado en reflexión de forma predeterminada. Esta deshabilitación es útil para evitar crear una raíz accidental de componentes de reflexión que ni siquiera están en uso, especialmente en aplicaciones AOT recortadas y nativas. Para deshabilitar la serialización basada en reflexión predeterminada requiriendo que se pase un argumento JsonSerializerOptions a los métodos de serialización y deserialización de JsonSerializer, establezca la propiedad JsonSerializerIsReflectionEnabledByDefault
de MSBuild en false
en el archivo de proyecto.
Utiliza la nueva API de IsReflectionEnabledByDefault para comprobar el valor del interruptor de funciones. Si es un autor de biblioteca que se basa en System.Text.Json, puede confiar en la propiedad para configurar los valores predeterminados evitando crear una raíz accidental de los componentes de reflexión.
Para obtener más información, vea Deshabilitar los valores predeterminados de reflexión.
Nuevos métodos de API de JsonNode
Los tipos JsonNode y System.Text.Json.Nodes.JsonArray incluyen los siguientes métodos nuevos.
public partial class JsonNode
{
// Creates a deep clone of the current node and all its descendants.
public JsonNode DeepClone();
// Returns true if the two nodes are equivalent JSON representations.
public static bool DeepEquals(JsonNode? node1, JsonNode? node2);
// Determines the JsonValueKind of the current node.
public JsonValueKind GetValueKind(JsonSerializerOptions options = null);
// If node is the value of a property in the parent
// object, returns its name.
// Throws InvalidOperationException otherwise.
public string GetPropertyName();
// If node is the element of a parent JsonArray,
// returns its index.
// Throws InvalidOperationException otherwise.
public int GetElementIndex();
// Replaces this instance with a new value,
// updating the parent object/array accordingly.
public void ReplaceWith<T>(T value);
// Asynchronously parses a stream as UTF-8 encoded data
// representing a single JSON value into a JsonNode.
public static Task<JsonNode?> ParseAsync(
Stream utf8Json,
JsonNodeOptions? nodeOptions = null,
JsonDocumentOptions documentOptions = default,
CancellationToken cancellationToken = default);
}
public partial class JsonArray
{
// Returns an IEnumerable<T> view of the current array.
public IEnumerable<T> GetValues<T>();
}
Miembros no públicos
Puede optar por miembros no públicos en el contrato de serialización de un tipo determinado mediante anotaciones de atributo JsonIncludeAttribute y JsonConstructorAttribute.
public static void NonPublicMembers()
{
string json = JsonSerializer.Serialize(new MyPoco(42));
Console.WriteLine(json);
// {"X":42}
JsonSerializer.Deserialize<MyPoco>(json);
}
public class MyPoco
{
[JsonConstructor]
internal MyPoco(int x) => X = x;
[JsonInclude]
internal int X { get; }
}
Para obtener más información, vea Usar tipos inmutables y descriptores de acceso y miembros no públicos.
API de deserialización de streaming
.NET 8 incluye nuevos métodos de extensión de deserialización de streaming IAsyncEnumerable<T>, por ejemplo GetFromJsonAsAsyncEnumerable. Existen métodos similares que devuelven Task<TResult>, por ejemplo, HttpClientJsonExtensions.GetFromJsonAsync. Los nuevos métodos de extensión invocan las API de streaming y devuelven IAsyncEnumerable<T>.
En el código siguiente se muestra cómo puede usar los nuevos métodos de extensión.
public async static void StreamingDeserialization()
{
const string RequestUri = "https://api.contoso.com/books";
using var client = new HttpClient();
IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);
await foreach (Book? book in books)
{
Console.WriteLine($"Read book '{book?.title}'");
}
}
public record Book(int id, string title, string author, int publishedYear);
Método de extensión WithAddedModifier
El nuevo método de extensión WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) permite introducir fácilmente modificaciones en los contratos de serialización de instancias arbitrarias de IJsonTypeInfoResolver
.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
Nuevas sobrecargas JsonContent.Create
Ahora puede crear instancias JsonContent mediante contratos seguros frente a recortes o generados por el origen. Los nuevos métodos son:
- JsonContent.Create(Object, JsonTypeInfo, MediaTypeHeaderValue)
- JsonContent.Create<T>(T, JsonTypeInfo<T>, MediaTypeHeaderValue)
var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);
public record Book(int id, string title, string author, int publishedYear);
[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}
Inmovilizar una instancia de JsonSerializerOptions
Los siguientes métodos nuevos te permiten tener control sobre cuándo se congela una instancia de JsonSerializerOptions:
JsonSerializerOptions.MakeReadOnly()
Esta sobrecarga está diseñada para ser segura frente a recortes y, por lo tanto, generará una excepción en los casos en que no se haya configurado un solucionador en la instancia de opciones.
JsonSerializerOptions.MakeReadOnly(Boolean)
Si pasa
true
a esta sobrecarga, rellena la instancia de opciones con la resolución de reflexión predeterminada si falta una. Este método está marcado comoRequiresUnreferenceCode
/RequiresDynamicCode
y, por tanto, no es adecuado para las aplicaciones AOT nativas.
La nueva propiedad IsReadOnly permite comprobar si la instancia de opciones está congelada.
Abstracción de tiempo
La nueva clase TimeProvider y la interfaz ITimer agregan la funcionalidad de abstracción de tiempo, lo que permite simular el tiempo en escenarios de prueba. Además, puede usar la abstracción de tiempo para simular Task operaciones que dependen de la progresión del tiempo mediante Task.Delay y Task.WaitAsync. La abstracción de tiempo admite las siguientes operaciones de tiempo esenciales:
- Recuperación de la hora local y UTC
- Obtención de una marca de tiempo para medir el rendimiento
- Creación de un temporizador
En el siguiente fragmento de código se muestran algunos ejemplos de uso.
// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();
TimerCallback callback = s => ((State)s!).Signal();
// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();
TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;
public override TimeZoneInfo LocalTimeZone => _zoneInfo;
public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
new ZonedTimeProvider(zoneInfo);
}
Mejoras de UTF8
Si quiere habilitar la escritura de una representación similar a una cadena del tipo en un intervalo de destino, implemente la nueva interfaz IUtf8SpanFormattable en el tipo. Esta nueva interfaz está estrechamente relacionada con ISpanFormattable, pero tiene como destino UTF8 y Span<byte>
en lugar de UTF16 y Span<char>
.
IUtf8SpanFormattable se ha implementado en todos los tipos primitivos (y otros más), con la misma lógica compartida exactamente si el destino es string
, Span<char>
o Span<byte>
. Tiene compatibilidad completa con todos los formatos (incluido el nuevo especificador binario "B" ) y todas las referencias culturales. Esto significa que ahora puede dar formato directamente a UTF-8 desde Byte
, Complex
, Char
, DateOnly
, DateTime
, DateTimeOffset
, Decimal
, Double
, Guid
, Half
, IPAddress
, IPNetwork
, Int16
, Int32
, Int64
, Int128
, IntPtr
, NFloat
, SByte
, Single
, Rune
, TimeOnly
, TimeSpan
, UInt16
, UInt32
, UInt64
, UInt128
, UIntPtr
y Version
.
Los nuevos métodos de Utf8.TryWrite proporcionan un homólogo basado en UTF8 a los métodos de MemoryExtensions.TryWrite existentes, basados en UTF16. Puede usar la sintaxis de cadena interpolada para dar formato a una expresión compleja directamente en un intervalo de bytes UTF8, por ejemplo:
static bool FormatHexVersion(
short major,
short minor,
short build,
short revision,
Span<byte> utf8Bytes,
out int bytesWritten) =>
Utf8.TryWrite(
utf8Bytes,
CultureInfo.InvariantCulture,
$"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
out bytesWritten);
La implementación reconoce IUtf8SpanFormattable en los valores de formato y usa sus implementaciones para escribir sus representaciones UTF8 directamente en el intervalo de destino.
La implementación también utiliza el nuevo método de Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), que junto con su homólogo de Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32), admite la codificación y descodificación en un intervalo de destino. Si el intervalo no es lo suficientemente largo como para contener el estado resultante, los métodos devuelven false
en lugar de iniciar una excepción.
Métodos para trabajar con aleatoriedad
Los tipos System.Random y System.Security.Cryptography.RandomNumberGenerator presentan dos nuevos métodos para trabajar con aleatoriedad.
GetItems<T>()
Los nuevos métodos System.Random.GetItems y System.Security.Cryptography.RandomNumberGenerator.GetItems permiten elegir aleatoriamente un número especificado de elementos de un conjunto de entrada. En el ejemplo siguiente se muestra cómo usar System.Random.GetItems<T>()
(en la instancia proporcionada por la propiedad Random.Shared) para insertar aleatoriamente 31 elementos en una matriz. Este ejemplo podría usarse en un juego de "Simon", donde los jugadores deben recordar una secuencia de botones coloreados.
private static ReadOnlySpan<Button> s_allButtons = new[]
{
Button.Red,
Button.Green,
Button.Blue,
Button.Yellow,
};
// ...
Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...
Shuffle<T>()
Los nuevos métodos Random.Shuffle y RandomNumberGenerator.Shuffle<T>(Span<T>) le permiten randomizar el orden de un rango. Estos métodos son útiles para reducir el sesgo de entrenamiento en el aprendizaje automático (de modo que la primera etapa no siempre sea el entrenamiento y la última no siempre sea la prueba).
YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);
IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);
DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);
IDataView predictions = model.Transform(split.TestSet);
// ...
Tipos centrados en el rendimiento
.NET 8 presenta varios tipos nuevos destinados a mejorar el rendimiento de la aplicación.
El nuevo espacio de nombres System.Collections.Frozen incluye los tipos de colección FrozenDictionary<TKey,TValue> y FrozenSet<T>. Estos tipos no permiten cambios en las claves y los valores una vez creada una colección. Ese requisito permite operaciones de lectura más rápidas (por ejemplo,
TryGetValue()
). Además, estos tipos son especialmente útiles para las colecciones que se rellenan en el primer uso y que se conservan durante un servicio de larga duración, por ejemplo:private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Métodos como MemoryExtensions.IndexOfAny buscan la primera aparición de cualquier valor en la colección pasada. El nuevo tipo de System.Buffers.SearchValues<T> está diseñado para pasarse a dichos métodos. En consecuencia, .NET 8 agrega nuevas sobrecargas de métodos como MemoryExtensions.IndexOfAny que aceptan una instancia del nuevo tipo. Cuando se crea una instancia de SearchValues<T>, todos los datos necesarios para optimizar las búsquedas posteriores se obtienen en ese momento, lo que significa que el trabajo se realiza por adelantado.
El nuevo tipo de System.Text.CompositeFormat es útil para optimizar las cadenas de formato que no se conocen en tiempo de compilación (por ejemplo, si la cadena de formato se carga desde un archivo de recursos). Un poco de tiempo adicional se invierte de antemano en realizar tareas, como analizar la cadena de texto, lo que evita tener que realizar este trabajo cada vez que se use.
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
Los nuevos tipos de System.IO.Hashing.XxHash3 y System.IO.Hashing.XxHash128 proporcionan implementaciones de los algoritmos hash XXH3 y XXH128 rápidos.
System.Numerics y System.Runtime.Intrinsics
En esta sección se tratan las mejoras de los espacios de nombres System.Numerics y System.Runtime.Intrinsics.
- Vector256<T>, Matrix3x2y Matrix4x4 han mejorado la aceleración de hardware en .NET 8. Por ejemplo, se ha vuelto a implementar Vector256<T> para tener operaciones
2x Vector128<T>
internas, siempre que sea posible. Esto permite la aceleración parcial de algunas funciones cuandoVector128.IsHardwareAccelerated == true
peroVector256.IsHardwareAccelerated == false
, como en Arm64. - Los intrínsecos de hardware ahora se anotan con el atributo
ConstExpected
. Esto garantiza que los usuarios sean conscientes de que el hardware subyacente espera una constante y, por lo tanto, cuando un valor no constante puede dañar inesperadamente el rendimiento. - La API de Lerp(TSelf, TSelf, TSelf)
Lerp
se ha agregado a IFloatingPointIeee754<TSelf> y, por tanto, afloat
(Single),double
(Double) y Half. Esta API permite realizar una interpolación lineal entre dos valores de forma eficaz y correcta.
Vector512 y AVX-512
.NET Core 3.0 amplió la compatibilidad con SIMD para incluir las API intrínsecas de hardware específicas de la plataforma para x86/x64. .NET 5 agregó compatibilidad con Arm64 y .NET 7 agregó los intrínsecos de hardware multiplataforma. .NET 8 amplía la compatibilidad con SIMD al introducir Vector512<T> y admitir las instrucciones Extensiones de vector avanzadas 512 (AVX-512) de Intel.
En concreto, .NET 8 incluye compatibilidad con las siguientes características clave de AVX-512:
- Operaciones vectoriales de 512 bits
- 16 registros SIMD adicionales
- Instrucciones adicionales disponibles para vectores de 128 bits, 256 y 512 bits
Si tiene hardware que admite la funcionalidad, Vector512.IsHardwareAccelerated ahora notifica true
.
.NET 8 también agrega varias clases específicas de la plataforma en el espacio de nombres System.Runtime.Intrinsics.X86:
- Avx512F (básica)
- Avx512BW (byte y palabra)
- Avx512CD (detección de conflictos)
- Avx512DQ (palabra doble y palabra cuádruple)
- Avx512Vbmi (instrucciones de manipulación de bytes vectoriales)
Estas clases siguen la misma forma general que otras arquitecturas de conjuntos de instrucciones (ISA) en que exponen una propiedad IsSupported y una clase Avx512F.X64 anidada para obtener instrucciones disponibles solo para procesos de 64 bits. Además, cada clase tiene una clase Avx512F.VL anidada que proporciona las extensiones Avx512VL
(longitud vectorial) para el conjunto de instrucciones correspondiente.
Incluso si no usas instrucciones específicas de Vector512
o Avx512F
en tu código, es probable que aún te beneficies de la nueva compatibilidad con AVX-512. El JIT puede aprovechar las instrucciones e registros adicionales implícitamente al usar Vector128<T> o Vector256<T>. La biblioteca de clases base usa estos intrínsecos de hardware internamente en la mayoría de las operaciones expuestas por Span<T> y ReadOnlySpan<T> y en muchas de las API matemáticas expuestas para los tipos primitivos.
Validación de datos
El espacio de nombres System.ComponentModel.DataAnnotations incluye nuevos atributos de validación de datos destinados a escenarios de validación en servicios nativos en la nube. Aunque los validadores de DataAnnotations
preexistentes están orientados a la validación típica de entrada de datos de la interfaz de usuario, como los campos de un formulario, los nuevos atributos están diseñados para validar datos que no son de entrada de usuario, como opciones de configuración. Además de los nuevos atributos, se agregaron nuevas propiedades al tipo RangeAttribute.
Nueva API | Descripción |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Especifica si los límites se incluyen en el intervalo permitido. |
System.ComponentModel.DataAnnotations.LengthAttribute | Especifica los límites inferiores y superiores de las cadenas o colecciones. Por ejemplo, [Length(10, 20)] requiere al menos 10 elementos y como máximo 20 elementos en una colección. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Valida que una cadena es una representación válida de Base64. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Especifique listas de permitidos y listas de denegación, respectivamente. Por ejemplo, [AllowedValues("apple", "banana", "mango")] . |
Métricas
Las nuevas API permiten adjuntar etiquetas de par clave-valor a Meter y Instrument objetos al crearlas. Los agregadores de medidas de métricas publicadas pueden usar las etiquetas para diferenciar los valores agregados.
var options = new MeterOptions("name")
{
Version = "version",
// Attach these tags to the created meter.
Tags = new TagList()
{
{ "MeterKey1", "MeterValue1" },
{ "MeterKey2", "MeterValue2" }
}
};
Meter meter = meterFactory!.Create(options);
Counter<int> counterInstrument = meter.CreateCounter<int>(
"counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);
Las nuevas API incluyen:
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
Criptografía
.NET 8 agrega compatibilidad con los primitivos hash SHA-3. (SHA-3 es compatible actualmente con Linux con OpenSSL 1.1.1 o posterior y Windows 11 Build 25324 o posterior). Las API en las que SHA-2 está disponible ahora ofrecen un complemento SHA-3. Esto incluye SHA3_256
, SHA3_384
y SHA3_512
para el hash; HMACSHA3_256
, HMACSHA3_384
y HMACSHA3_512
para HMAC; HashAlgorithmName.SHA3_256
, HashAlgorithmName.SHA3_384
y HashAlgorithmName.SHA3_512
para el hash en el que se puede configurar el algoritmo; y RSAEncryptionPadding.OaepSHA3_256
, RSAEncryptionPadding.OaepSHA3_384
y RSAEncryptionPadding.OaepSHA3_512
para el cifrado OAEP rsa.
En el ejemplo siguiente se muestra cómo usar las API, incluida la propiedad SHA3_256.IsSupported
para determinar si la plataforma admite SHA-3.
// Hashing example
if (SHA3_256.IsSupported)
{
byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
// ...
}
// Signing example
if (SHA3_256.IsSupported)
{
using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
// ...
}
La compatibilidad con SHA-3 está destinada actualmente a admitir primitivos criptográficos. No se espera que las construcciones y protocolos de nivel superior admitan completamente SHA-3 inicialmente. Estos protocolos incluyen certificados X.509, SignedXmly COSE.
Gestión de redes
Compatibilidad con el proxy HTTPS
Hasta ahora, los tipos de proxy que admitía HttpClient permitían a un "intermediario" ver a qué sitio se conecta el cliente, incluso para las URIs HTTPS. HttpClient ahora admite proxy HTTPS, que crea un canal cifrado entre el cliente y el proxy para que todas las solicitudes se puedan controlar con plena privacidad.
Para habilitar el proxy HTTPS, establezca la variable de entorno all_proxy
o use la clase WebProxy para controlar el proxy mediante programación.
Unix: export all_proxy=https://x.x.x.x:3218
Windows: set all_proxy=https://x.x.x.x:3218
También puede usar la clase WebProxy para controlar el proxy mediante programación.
Métodos ZipFile basados en secuencias
.NET 8 incluye nuevas sobrecargas de ZipFile.CreateFromDirectory que le permiten recopilar todos los archivos incluidos en un directorio y comprimirlos y, a continuación, almacenar el archivo ZIP resultante en la secuencia proporcionada. De forma similar, las nuevas sobrecargas ZipFile.ExtractToDirectory permiten proporcionar una secuencia que contiene un archivo comprimido y extraer su contenido en el sistema de archivos. Estas son las nuevas sobrecargas:
namespace System.IO.Compression;
public static partial class ZipFile
{
public static void CreateFromDirectory(
string sourceDirectoryName, Stream destination);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory,
Encoding? entryNameEncoding);
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, bool overwriteFiles) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}
Estas nuevas API pueden ser útiles cuando se restringe el espacio en disco, ya que evitan tener que usar el disco como paso intermedio.
Bibliotecas de extensiones
Esta sección contiene los subtemas siguientes:
- Validación de opciones
- Constructores LoggerMessageAttribute
- Métricas de extensiones
- Servicios de ciclo de vida hospedados
- Servicios de inserción de dependencias con claves
- System.Numerics.Tensors.TensorPrimitives
Servicios de inserción de dependencias con claves
Los servicios de inyección de dependencias con claves (DI) ofrecen un medio para registrar y recuperar servicios DI usando claves. Mediante el uso de claves, puede definir el ámbito de cómo registrar y consumir servicios. Estas son algunas de las nuevas API:
- Interfaz IKeyedServiceProvider.
- Atributo ServiceKeyAttribute, que se puede usar para insertar la clave que se usó para el registro o resolución en el constructor.
- Atributo FromKeyedServicesAttribute, que se puede usar en los parámetros del constructor de servicio para especificar qué servicio con clave se va a usar.
- Varios métodos de extensión nuevos para que IServiceCollection admita servicios con claves, por ejemplo, ServiceCollectionServiceExtensions.AddKeyedScoped.
- La implementación ServiceProvider de IKeyedServiceProvider.
En el ejemplo siguiente se muestra cómo usar los servicios DI con claves.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();
class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
public object? GetData() => cache.Get("data");
}
class SmallCacheConsumer(IServiceProvider serviceProvider)
{
public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
Para obtener más información, vea dotnet/runtime#64427.
Servicios de ciclo de vida hospedados
Los servicios hospedados ahora tienen más opciones para la ejecución durante el ciclo de vida de la aplicación. IHostedService proporcionó StartAsync
y StopAsync
, y ahora IHostedLifecycleService proporciona estos métodos adicionales:
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
Estos métodos se ejecutan antes y después de los puntos existentes, respectivamente.
En el ejemplo siguiente se muestra cómo usar las nuevas API.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
internal class HostedLifecycleServices
{
public async static void RunIt()
{
IHostBuilder hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
});
using (IHost host = hostBuilder.Build())
{
await host.StartAsync();
}
}
public class MyService : IHostedLifecycleService
{
public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
}
}
Para obtener más información, vea dotnet/runtime#86511.
Validación de opciones
Generador de código fuente
Para reducir la sobrecarga de inicio y mejorar el conjunto de características de validación, hemos introducido un generador de código fuente que implementa la lógica de validación. En el código siguiente se muestran modelos de ejemplo y clases de validador.
public class FirstModelNoNamespace
{
[Required]
[MinLength(5)]
public string P1 { get; set; } = string.Empty;
[Microsoft.Extensions.Options.ValidateObjectMembers(
typeof(SecondValidatorNoNamespace))]
public SecondModelNoNamespace? P2 { get; set; }
}
public class SecondModelNoNamespace
{
[Required]
[MinLength(5)]
public string P4 { get; set; } = string.Empty;
}
[OptionsValidator]
public partial class FirstValidatorNoNamespace
: IValidateOptions<FirstModelNoNamespace>
{
}
[OptionsValidator]
public partial class SecondValidatorNoNamespace
: IValidateOptions<SecondModelNoNamespace>
{
}
Si la aplicación usa la inserción de dependencias, puede insertar la validación como se muestra en el código de ejemplo siguiente.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
builder.Configuration.GetSection("some string"));
builder.Services.AddSingleton<
IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();
Tipo ValidateOptionsResultBuilder
.NET 8 presenta el tipo de ValidateOptionsResultBuilder para facilitar la creación de un objeto ValidateOptionsResult. Es importante destacar que este constructor permite la acumulación de varios errores. Anteriormente, la creación del objeto ValidateOptionsResult necesario para implementar IValidateOptions<TOptions>.Validate(String, TOptions) era difícil y, a veces, provocaba errores de validación en capas. Si se produjeron varios errores, el proceso de validación a menudo se detuvo en el primer error.
En el fragmento de código siguiente se muestra un ejemplo de uso de ValidateOptionsResultBuilder.
ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");
// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();
// Reset the builder to allow using it in new validation operation.
builder.Clear();
Constructores LoggerMessageAttribute
LoggerMessageAttribute ahora ofrece sobrecargas de compilador adicionales. Anteriormente, tenía que elegir el constructor sin parámetros o el constructor que requería todos los parámetros (id. de evento, nivel de registro y mensaje). Las nuevas sobrecargas ofrecen mayor flexibilidad al especificar los parámetros necesarios con código reducido. Si no proporciona un identificador de evento, el sistema genera uno automáticamente.
public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);
Métricas de extensiones
Interfaz IMeterFactory
Puede registrar la nueva interfaz de IMeterFactory en contenedores de inyección de dependencias (DI) y usarla para crear objetos Meter de forma aislada.
Registre la interfaz IMeterFactory en el contenedor de inserción de dependencias mediante la implementación predeterminada del generador de medidores:
// 'services' is the DI IServiceCollection.
services.AddMetrics();
A continuación, los consumidores pueden obtener el generador de medidores y usarlo para crear un nuevo objeto Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Clase MetricCollector<T>
La nueva clase MetricCollector<T> permite registrar mediciones de métricas junto con marcas de tiempo. Además, la clase ofrece la flexibilidad de usar un proveedor de tiempo de su elección para generar una marca de tiempo precisa.
const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;
var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);
Assert.IsNull(collector.LastMeasurement);
counter.Add(3);
// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);
Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);
System.Numerics.Tensors.TensorPrimitives
El paquete NuGet actualizado de System.Numerics.Tensors incluye las API en el nuevo tipo System.Numerics.Tensors.TensorPrimitives que agrega compatibilidad con las operaciones de tensor. Los primitivos de tensor optimizan cargas de trabajo que consumen muchos datos, como las de inteligencia artificial y aprendizaje automático.
Las cargas de trabajo de inteligencia artificial, como la búsqueda semántica y la generación aumentada de recuperación (RAG) amplían las funcionalidades de lenguaje natural de los modelos de lenguaje grande, como ChatGPT, aumentando las solicitudes con datos relevantes. Para estas cargas de trabajo, las operaciones sobre vectores (como la similitud de coseno para encontrar los datos más relevantes para responder a una pregunta) son cruciales. El tipo de TensorPrimitives proporciona API para las operaciones vectoriales.
Para obtener más información, consulte la entrada de blog Anunciando .NET 8 RC 2.
Compatibilidad nativa con AOT
La opción para publicar como AOT nativa se introdujo por primera vez en .NET 7. La publicación de una aplicación con AOT nativo crea una versión totalmente independiente de la aplicación que no necesita un entorno de ejecución, todo se incluye en un único archivo. .NET 8 aporta las siguientes mejoras a la publicación nativa de AOT:
Agrega compatibilidad con las arquitecturas x64 y Arm64 en macOS.
Reduce los tamaños de las aplicaciones AOT nativas en Linux hasta un 50 %%. En la tabla siguiente se muestra el tamaño de una aplicación "Hola mundo" publicada con AOT nativo que incluye todo el entorno de ejecución de .NET en .NET 7 frente a .NET 8:
Sistema operativo .NET 7 .NET 8 Linux x64 (con -p:StripSymbols=true
)3,76 MB 1,84 MB Windows x64 2,85 MB 1,77 MB Permite especificar una preferencia de optimización: tamaño o velocidad. De forma predeterminada, el compilador elige generar código rápido mientras se tiene en cuenta el tamaño de la aplicación. Sin embargo, puede usar la propiedad
<OptimizationPreference>
MSBuild para optimizar específicamente para una u otra. Para obtener más información, consulte Optimizar implementaciones de AOT.
Dirigirse a plataformas similares a iOS con AOT nativo
.NET 8 inicia el trabajo para habilitar la compatibilidad con AOT nativo para plataformas similares a iOS. Ahora puede compilar y ejecutar aplicaciones .NET iOS y .NET MAUI con AOT nativo en las plataformas siguientes:
ios
iossimulator
maccatalyst
tvos
tvossimulator
Las pruebas preliminares muestran que el tamaño de la aplicación en el disco disminuye aproximadamente en 35% para aplicaciones de .NET iOS que usan AOT nativo en lugar de Mono. El tamaño de la aplicación en el disco para aplicaciones de .NET MAUI para iOS disminuye hasta un 50 %. Además, el tiempo de inicio también es más rápido. Las aplicaciones de .NET iOS tienen un tiempo de inicio aproximadamente un 28%% más rápido, mientras que las aplicaciones de .NET MAUI iOS tienen aproximadamente un 50%% mejor rendimiento de inicio en comparación con Mono. La compatibilidad con .NET 8 es experimental y solo el primer paso para la característica en su conjunto. Para obtener más información, consulte la entrada de blog mejoras de rendimiento de .NET 8 en .NET MAUI.
La compatibilidad nativa con AOT está disponible como una característica de participación destinada a la implementación de aplicaciones; Mono sigue siendo el entorno de ejecución predeterminado para el desarrollo y la implementación de aplicaciones. Para compilar y ejecutar una aplicación .NET MAUI con AOT nativo en un dispositivo iOS, use dotnet workload install maui
para instalar la carga de trabajo de .NET MAUI y dotnet new maui -n HelloMaui
para crear la aplicación. A continuación, establezca la propiedad MSBuild PublishAot
en true
en el archivo del proyecto.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Al establecer la propiedad necesaria y ejecutar dotnet publish
como se muestra en el ejemplo siguiente, la aplicación se implementará mediante AOT nativo.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Limitaciones
No todas las características de iOS son compatibles con AOT nativo. Del mismo modo, no todas las bibliotecas que se usan habitualmente en iOS son compatibles con NativeAOT. Además de las limitaciones de la implementación de la AOT nativa existentes, en la lista siguiente se muestran algunas de las otras limitaciones al dirigirse a plataformas similares a iOS:
- El uso de AOT nativo solo está habilitado durante la implementación de la aplicación (
dotnet publish
). - La depuración de código administrado solo se admite con Mono.
- La compatibilidad con el marco MAUI de .NET es limitada.
Compilación AOT para aplicaciones Android
Para reducir el tamaño de la aplicación, las aplicaciones .NET y .NET MAUI que tienen como destino Android usan el modo de compilación anticipada (AOT) con perfiles cuando están integradas en modo de versión. La compilación AOT basada en perfiles afecta a menos métodos que la compilación AOT normal. .NET 8 presenta la propiedad <AndroidStripILAfterAOT>
que permite participar en la compilación de AOT adicional para aplicaciones Android para reducir el tamaño de la aplicación aún más.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
De forma predeterminada, establecer AndroidStripILAfterAOT
en true
invalida la configuración de AndroidEnableProfiledAot
predeterminada, lo que permite recortar (casi) todos los métodos compilados por AOT. También puede usar AOT con perfiles y la eliminación de IL conjuntamente estableciendo explícitamente ambas propiedades en true
:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
Aplicaciones de Windows desarrolladas multiplataforma
Al compilar aplicaciones destinadas a Windows en plataformas que no son windows, el archivo ejecutable resultante ahora se actualiza con cualquier recurso Win32 especificado, por ejemplo, icono de aplicación, manifiesto, información de versión.
Anteriormente, las aplicaciones tenían que compilarse en Windows para tener estos recursos. La corrección de esta brecha en la compatibilidad entre edificios ha sido una solicitud popular, ya que era un punto importante que afectaba tanto a la complejidad de la infraestructura como al uso de recursos.