Новые возможности среды выполнения .NET 8
В этой статье описываются новые функции среды выполнения .NET для .NET 8.
Улучшения производительности
.NET 8 включает улучшения для создания кода и JIT-компиляции:
- Улучшения производительности Arm64
- Улучшения SIMD
- Поддержка расширений ISA AVX-512 (см. Vector512 и AVX-512)
- Улучшения на основе облака
- Улучшения пропускной способности JIT
- Цикл и общие оптимизации
- Оптимизированный доступ для полей, помеченных ThreadStaticAttribute
- Последовательное выделение регистров. Arm64 содержит две инструкции для поиска вектора таблицы, которые требуют, чтобы все сущности в кортеже операндов присутствовали в последовательных регистрах.
- JIT/NativeAOT теперь может разворачивать и автоматически векторизировать некоторые операции с памятью с использованием SIMD, такие как сравнение, копирование и обнуление, если удается определить их размеры на этапе компиляции.
Кроме того, улучшена оптимизация с помощью динамического профиля (PGO) и теперь включена по умолчанию. Для его включения больше не требуется использовать параметр конфигурации среды выполнения. Динамический PGO взаимодействует с многоуровневой компиляцией для дальнейшей оптимизации кода на основе дополнительной инструментовки, которая установлена на уровне 0.
В среднем производительность увеличивается примерно на 15%благодаря динамическому PGO. В эталонном наборе из ~4600 тестов, 23% улучшили производительность на 20% или более.
Продвижение структуры Codegen
.NET 8 включает новый проход оптимизации физического продвижения для генератора кода, который расширяет способность JIT улучшать переменные структуры. Эта оптимизация (также называется скалярное замещение агрегатов) заменяет поля переменных структуры на примитивные переменные, которые JIT затем может более точно понять и оптимизировать.
JIT уже поддерживает эту оптимизацию, но с несколькими большими ограничениями, в том числе:
- Он поддерживается только для структур с четырьмя или меньшими полями.
- Поддержка имелась только в том случае, если каждое поле было примитивным типом или простой структурой, оборачивающей примитивный тип.
Физическое продвижение устраняет эти ограничения, что решает ряд долгосрочных проблем JIT.
Сборка мусора
.NET 8 добавляет возможность настроить ограничение памяти на лету. Это полезно в сценариях облачной службы, где приходит и идет спрос. Чтобы быть экономически эффективным, службы должны увеличивать и уменьшать потребление ресурсов по мере колебания спроса. Когда служба обнаруживает снижение спроса, она может сократить потребление ресурсов, уменьшая его предел памяти. Ранее это не удалось, так как сборщик мусора (GC) не знал об изменении и может выделить больше памяти, чем новое ограничение. С помощью этого изменения можно вызвать API RefreshMemoryLimit() для обновления GC с новым ограничением памяти.
Существуют некоторые ограничения, которые следует учитывать:
- На 32-разрядных платформах (например, Windows x86 и Linux ARM) .NET не может установить новое жесткое ограничение кучи, если еще нет.
- API может вернуть код состояния, отличный от нуля, указывающий на сбой обновления. Это может произойти, если масштабирование слишком агрессивно и не оставляет места для GC маневрировать. В этом случае рассмотрите возможность вызова
GC.Collect(2, GCCollectionMode.Aggressive)
для сжатия текущего использования памяти, а затем повторите попытку. - Если увеличить предел памяти, превышающий размер, который GC считает, что процесс может обрабатываться во время запуска, вызов
RefreshMemoryLimit
будет успешным, но он не сможет использовать больше памяти, чем то, что он воспринимает как ограничение.
В следующем фрагменте кода показано, как вызвать API.
GC.RefreshMemoryLimit();
Можно также обновить некоторые параметры конфигурации GC, связанные с ограничением памяти. Следующий фрагмент кода задает жесткое ограничение кучи в 100 мебибайт (MiB):
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
API может вызвать InvalidOperationException, если жесткое ограничение недопустимо, например, в случае отрицательного процентного ограничения для жесткого ограничения кучи или если жесткое ограничение слишком низко. Это может произойти, если жёсткое ограничение на кучу, которое установит обновление, либо из-за новых параметров AppData, либо из-за изменений, подразумеваемых ограничением памяти контейнера, меньше, чем уже занято.
Глобализация для мобильных приложений
Мобильные приложения в iOS, tvOS и MacCatalyst могут выбрать новый режим гибридной глобализации, использующий более легкий пакет ICU. В гибридном режиме данные глобализации частично извлекаются из пакета ICU и частично из вызовов собственных API. Гибридный режим обслуживает все локали , поддерживаемые мобильными.
Гибридный режим наиболее подходит для приложений, которые не могут работать в невариантном режиме глобализации и используют язык и региональные параметры, которые были исключены из данных ICU на мобильных устройствах. Его также можно использовать при загрузке меньшего файла данных ICU. (Файл icudt_hybrid.dat равен 34,5 % меньше, чем файл данных ICU по умолчанию icudt.dat.)
Чтобы использовать режим гибридной глобализации, установите для свойства HybridGlobalization
MSBuild значение true.
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Существуют некоторые ограничения, которые следует учитывать:
- Из-за ограничений собственного API не все API глобализации поддерживаются в гибридном режиме.
- Некоторые поддерживаемые API имеют другое поведение.
Чтобы проверить, затронуто ли ваше приложение, см. различия в поведении.
Взаимодействие COM, созданное источником
.NET 8 включает новый генератор источников, поддерживающий взаимодействие с интерфейсами COM. С помощью GeneratedComInterfaceAttribute можно пометить интерфейс как COM-интерфейс для исходного генератора. Затем генератор исходного кода создаст код для обеспечения вызова из кода C# в неуправляемый код. Он также создает код для обеспечения вызова из неуправляемого кода в C#. Этот генератор исходного кода интегрируется с LibraryImportAttribute, и вы можете использовать типы с GeneratedComInterfaceAttribute как параметры и как типы возвращаемых значений в методах с атрибутами 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);
}
Генератор источников также поддерживает новый атрибут GeneratedComClassAttribute, чтобы позволить передавать типы, реализующие интерфейсы с атрибутом GeneratedComInterfaceAttribute в неуправляемый код. Генератор исходного кода создаст необходимый код для предоставления COM-объекта, который реализует интерфейсы и перенаправляет вызовы к управляемой реализации.
Методы в интерфейсах с атрибутом GeneratedComInterfaceAttribute поддерживают все те же типы, что и LibraryImportAttribute
, а LibraryImportAttribute
теперь поддерживает GeneratedComInterface
-атрибуты типов и GeneratedComClass
-атрибуты типов.
Если ваш код на C# использует интерфейс с атрибутом GeneratedComInterface
, чтобы либо обернуть COM-объект из неуправляемого кода, либо обернуть управляемый объект из C# для использования в неуправляемом коде, вы можете настроить, какой код будет сгенерирован, используя параметры свойства Options. Эти параметры означают, что вам не нужно писать маршаллеры для сценариев, которые, как вы знаете, не будут использоваться.
Генератор источника использует новый тип StrategyBasedComWrappers для создания и управления оболочками для COM-объектов и управляемых объектов. Этот новый тип управляет предоставлением ожидаемого пользовательского опыта в .NET для взаимодействия с COM, одновременно предоставляя расширяемые возможности для опытных пользователей. Если в вашем приложении есть собственный механизм определения типов из COM или если вам нужно поддерживать сценарии, которые в настоящее время не поддерживаются созданием кода для COM, рассмотрите возможность использования нового типа StrategyBasedComWrappers, чтобы добавить отсутствующие функции для вашего сценария и обеспечить такой же пользовательский интерфейс .NET для ваших типов COM.
Если вы используете Visual Studio, новые анализаторы и исправления кода упрощают преобразование существующего кода взаимодействия COM для использования исходного взаимодействия. Рядом с каждым интерфейсом, который имеет ComImportAttribute, лампочка предлагает возможность преобразования в автоматически сгенерированный код для взаимодействия. Исправление изменяет интерфейс для использования атрибута GeneratedComInterfaceAttribute. Рядом с каждым классом, реализующим интерфейс с GeneratedComInterfaceAttribute
, лампочка предлагает возможность добавить атрибут GeneratedComClassAttribute в тип. После преобразования типов, вы можете перенести методы DllImport
, чтобы использовать LibraryImportAttribute
.
Ограничения
Генератор источников COM не поддерживает сходство квартир, используя ключевое слово new
для активации COM CoClass и следующих API:
- интерфейсы на основе IDispatch.
- интерфейсы на основе IInspectable.
- Свойства и события COM.
Генератор исходного кода для привязки конфигурации
.NET 8 представляет генератор кода для предоставления удобной для AOT и обрезки конфигурации в ASP.NET Core. Генератор является альтернативой существующей реализации на основе отражения.
Исходный генератор ищет вызовы к Configure(TOptions), Bindи Get для получения сведений о типе. Если генератор включен в проекте, компилятор неявно выбирает созданные методы вместо существующих реализаций фреймворка на основе отражения.
Для использования генератора не требуются изменения исходного кода. Он включен по умолчанию в AOT-компилированных веб-приложениях, и когда PublishTrimmed
установлен в true
(приложения для .NET 8+). Для других типов проектов генератор исходного кода отключен по умолчанию, но вы можете выбрать, установив для свойства EnableConfigurationBindingGenerator
значение true
в файле проекта:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
В следующем коде показан пример вызова связывателя.
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; }
}
}
Основные библиотеки .NET
В этом разделе содержатся следующие подтопики:
- отражение
- сериализации
- абстракция времени
- улучшения UTF-8
- Методы для работы с случайностью
- типы, ориентированные на производительность,
- System.Numerics и System.Runtime.Intrinsics
- Валидация данных
- Метрики
- Криптография
- Сетевое взаимодействие
- методы ZipFile, основанные на потоках
Отражение
Указатели на функции были введены в .NET 5, однако соответствующая поддержка для отражения не была добавлена в то время. При использовании typeof
или отражения в указателе функции, например typeof(delegate*<void>())
или FieldInfo.FieldType
соответственно, возвращается IntPtr. Начиная с .NET 8, вместо этого возвращается объект System.Type. Этот тип предоставляет доступ к метаданным указателя функции, включая соглашения о вызовах, тип возврата и параметры.
Заметка
Экземпляр указателя функции, который является физическим адресом функции, продолжает представляться как IntPtr. Изменен только тип отражения.
Новые функциональные возможности в настоящее время реализованы только в среде выполнения CoreCLR и MetadataLoadContext.
В System.Typeдобавлены новые API, такие как IsFunctionPointer, а также для System.Reflection.PropertyInfo, System.Reflection.FieldInfoи System.Reflection.ParameterInfo. В следующем коде показано, как использовать некоторые из новых API для отражения.
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}");
}
}
}
}
В предыдущем примере создаются следующие выходные данные:
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
Сериализация
В .NET 8 внесены множество улучшений в функции сериализации и десериализации System.Text.Json. Например, можно настроить обработку свойств JSON, не входящих вPOCO.
В следующих разделах описаны другие улучшения сериализации:
- Поддержка встроенная для дополнительных типов
- генератор источника
- иерархии интерфейса
- правила именования
- свойства только для чтения
- Отключить основанное на отражении по умолчанию
- новые методы API JsonNode
- недоступных участников
- API десериализации потоковой передачи
- Метод расширения WithAddedModifier
- новые перегрузки JsonContent.Create
- Заморозить объект JsonSerializerOptions
Дополнительную информацию о JSON-сериализации в общем случае см. в статье о сериализации и десериализации JSON в .NET.
Встроенная поддержка дополнительных типов
Сериализатор имеет встроенную поддержку следующих дополнительных типов.
Half, Int128и UInt128 числовых типов.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Значения Memory<T> и ReadOnlyMemory<T>. Значения
byte
сериализуются в строки Base64, а другие типы — в массивы JSON.JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID" JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
Генератор источника
.NET 8 включает усовершенствования генератора исходного кода System.Text.Json , направленные на создание собственного интерфейса AOT на уровне сериализатора на основе отражения. Например:
Теперь генератор источников поддерживает сериализацию типов с
required
иinit
свойствами. Обе они уже поддерживались в сериализации на основе отражения.Улучшено форматирование исходного кода.
JsonSourceGenerationOptionsAttribute равенство функционала с JsonSerializerOptions. Дополнительные сведения см. в разделе Указание параметров (создание источника).
Дополнительная диагностика (например, SYSLIB1034 и SYSLIB1039).
Не включать типы игнорируемых или недоступных свойств.
Поддержка вложенных объявлений
JsonSerializerContext
в произвольных типах.Поддержка созданных компилятором или неизменяемых типов в сценариях создания слабо типизированных источников. Так как созданные компилятором типы не могут быть явно указаны генератором источника, System.Text.Json теперь выполняет разрешение ближайшего предка во время выполнения. Это разрешение определяет наиболее подходящий супертип, с помощью которого сериализуется значение.
Новый тип преобразователя
JsonStringEnumConverter<TEnum>
. Существующий класс JsonStringEnumConverter не поддерживается в Native AOT. Вы можете аннотировать ваши типы перечислений следующим образом:[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))] public enum MyEnum { Value1, Value2, Value3 } [JsonSerializable(typeof(MyEnum))] public partial class MyContext : JsonSerializerContext { }
Дополнительную информацию см. в разделе Преобразование полей перечисления в строковый формат.
Новое свойство
JsonConverter.Type
позволяет искать тип не универсального экземпляраJsonConverter
:Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
Свойство может быть пустым, так как оно возвращает
null
для экземпляровJsonConverterFactory
иtypeof(T)
для экземпляровJsonConverter<T>
.
Генераторы цепей источников
Класс JsonSerializerOptions включает новое свойство TypeInfoResolverChain, которое дополняет существующее свойство TypeInfoResolver. Эти свойства используются в настройке контракта для связывания генераторов исходного кода. Добавление нового свойства означает, что вам не нужно указывать все связанные компоненты на одном месте вызова — их можно добавить после вызова. TypeInfoResolverChain также позволяет осуществлять интроспекцию цепочки или удалять компоненты из неё. Дополнительные сведения см. в разделе Объединение генераторов источников.
Кроме того, JsonSerializerOptions.AddContext<TContext>() теперь устарело. Он заменен свойствами TypeInfoResolver и TypeInfoResolverChain. Дополнительные сведения см. в SYSLIB0049.
Иерархии интерфейсов
.NET 8 добавляет поддержку сериализации свойств из иерархий интерфейса.
В следующем коде показан пример, в котором свойства как из немедленно реализованного интерфейса, так и его базового интерфейса сериализуются.
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; }
}
Политика именования
JsonNamingPolicy
включает новые политики именования, которые охватывают преобразования имен свойств для snake_case
(с символом подчеркивания) и kebab-case
(с дефисом). Используйте эти политики аналогично существующей политике JsonNamingPolicy.CamelCase:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Дополнительные сведения см. в статье Использование встроенной политики именования.
Свойства только для чтения
Теперь можно десериализировать поля или свойства только для чтения (т. е. те, у которых нет set
доступа).
Чтобы воспользоваться этой поддержкой глобально, установите новый параметр PreferredObjectCreationHandlingна JsonObjectCreationHandling.Populate. Если совместимость является проблемой, вы также можете более детально включить функцию, разместив атрибут [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
на конкретные типы, свойства которых должны быть заполнены, или на отдельных свойствах.
Например, рассмотрим следующий код, который десериализируется в тип CustomerInfo
с двумя свойствами только для чтения.
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"
};
}
До .NET 8 входные значения были проигнорированы, а свойства Names
и Company
сохранили значения по умолчанию.
{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}
Теперь входные значения используются для заполнения свойств только для чтения во время десериализации.
{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}
Для получения более подробной информации о поведении десериализации при заполнении, см. раздел Заполнение инициализированных свойств.
Отключение стандартной функции, основанной на отражении
Теперь сериализатор на основе отражения можно отключить по умолчанию. Отключение этой функции полезно для предотвращения случайного закрепления компонентов отражения, которые даже не используются, особенно в оптимизированных и нативных приложениях AOT. Чтобы отключить сериализацию по умолчанию на основе отражения, требуя передачи аргумента JsonSerializerOptions в методы сериализации и десериализации JsonSerializer, задайте в файле проекта для свойства MSBuild JsonSerializerIsReflectionEnabledByDefault
значение false
.
Используйте новый API IsReflectionEnabledByDefault, чтобы проверить значение коммутатора функций. Если вы являетесь автором библиотеки, построенной на основе System.Text.Json, вы можете полагаться на свойство, чтобы настроить значения по умолчанию без случайного корневого отражения компонентов.
Дополнительные сведения см. в разделе Отключение отражения по умолчанию.
Новые методы API JsonNode
Типы JsonNode и System.Text.Json.Nodes.JsonArray включают следующие новые методы.
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>();
}
Непубличные члены
Вы можете включить невидимые для внешнего мира члены в контракт сериализации для данного типа с помощью аннотаций атрибутов JsonIncludeAttribute и 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; }
}
Дополнительные сведения см. в разделе Использовать неизменяемые типы и непубличные члены и методы доступа.
API десериализации потоковой передачи
.NET 8 включает новые методы расширения десериализации потоковой передачи IAsyncEnumerable<T>, например GetFromJsonAsAsyncEnumerable. Аналогичные методы существовали, возвращающие Task<TResult>, например, HttpClientJsonExtensions.GetFromJsonAsync. Новые методы расширения вызывают API потоковой передачи и возвращают IAsyncEnumerable<T>.
В следующем коде показано, как можно использовать новые методы расширения.
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);
Метод расширения WithAddedModifier
Новый метод расширения WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) позволяет легко вносить изменения в контракты сериализации произвольных экземпляров IJsonTypeInfoResolver
.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
Новые перегрузки JsonContent.Create
Теперь вы можете создавать экземпляры JsonContent с помощью контрактов, безопасных для обрезки, или сгенерированных из исходного кода. Новые методы:
- 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
{
}
Замораживание экземпляра JsonSerializerOptions
Следующие новые методы позволяют контролировать, когда экземпляр JsonSerializerOptions заморожен:
JsonSerializerOptions.MakeReadOnly()
Эта перегрузка предназначена для устойчивости к обрезкам и поэтому выбрасывает исключение в случаях, когда экземпляр параметров не настроен с помощью резолвера.
JsonSerializerOptions.MakeReadOnly(Boolean)
Если вы передаете
true
этой перегрузке, и резольвер отражения отсутствует, он заполняет экземпляр параметров резольвером по умолчанию. Этот метод помечаетсяRequiresUnreferenceCode
/RequiresDynamicCode
и поэтому не подходит для приложений Native AOT.
Новое свойство IsReadOnly позволяет проверить, является ли экземпляр параметров замороженным.
Абстракция времени
Новый класс TimeProvider и интерфейс ITimer добавляют функциональность абстракции времени , что позволяет имитировать время в сценариях тестирования. Кроме того, можно использовать абстракции времени для имитации Task операций, которые зависят от прогресса во времени с помощью Task.Delay и Task.WaitAsync. Абстракция времени поддерживает следующие основные операции времени:
- Получение локального и UTC-времени
- Получение метки времени для измерения производительности
- Создание таймера
В следующем фрагменте кода показаны некоторые примеры использования.
// 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);
}
Улучшения UTF8
Если вы хотите включить вывод строкового представления вашего типа в целевой интервал, реализуйте новый интерфейс IUtf8SpanFormattable на вашем типе. Этот новый интерфейс тесно связан с ISpanFormattable, но предназначен для UTF8 и Span<byte>
вместо UTF16 и Span<char>
.
IUtf8SpanFormattable был реализован для всех примитивных типов (а также других) с той же общей логикой, независимо от того, нацелено ли на string
, Span<char>
или Span<byte>
. Она имеет полную поддержку всех форматов (включая новый двоичный спецификатор "B" ) и всех культур. Это означает, что теперь можно форматировать непосредственно в UTF8 из 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
и Version
.
Новые методы Utf8.TryWrite предоставляют аналогу на основе UTF8 существующим методам MemoryExtensions.TryWrite, которые основаны на UTF16. Можно использовать интерполированный синтаксис строки для форматирования сложного выражения непосредственно в диапазоне байтов UTF8, например:
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);
Реализация распознаёт форматные значения IUtf8SpanFormattable и использует соответствующие реализации для записи их UTF8 представления прямо в целевой участок.
Реализация также использует новый метод Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), который вместе с его Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) аналогом поддерживает кодировку и декодирование в целевой диапазон. Если диапазон не достаточно длинный для хранения результирующего состояния, методы возвращают false
, а не вызывают исключение.
Методы для работы с случайностью
Типы System.Random и System.Security.Cryptography.RandomNumberGenerator вводят два новых метода для работы с случайностью.
GetItems<T>()
Новые методы System.Random.GetItems и System.Security.Cryptography.RandomNumberGenerator.GetItems позволяют случайным образом выбрать указанное количество элементов из входного набора. В следующем примере показано, как использовать System.Random.GetItems<T>()
(на экземпляре, предоставленном свойством Random.Shared), для случайного вставки 31 элементов в массив. Этот пример можно использовать в игре Simon, где игроки должны помнить последовательность цветных кнопок.
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 ...
Перетасовка<T>()
Новые методы Random.Shuffle и RandomNumberGenerator.Shuffle<T>(Span<T>) позволяют случайным образом упорядочивать диапазон. Эти методы полезны для уменьшения предвзятости в обучении машин, чтобы первым делом не всегда было обучение, а последним - тест.
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);
// ...
Типы, ориентированные на производительность
.NET 8 представляет несколько новых типов, направленных на повышение производительности приложения.
Новое пространство имен System.Collections.Frozen включает типы коллекций FrozenDictionary<TKey,TValue> и FrozenSet<T>. Эти типы не позволяют вносить изменения в ключи и значения после создания коллекции. Это требование позволяет ускорить операции чтения (например,
TryGetValue()
). Эти типы особенно полезны для коллекций, которые заполняются при первом использовании, а затем сохраняются в течение длительной службы, например:private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Такие методы, как MemoryExtensions.IndexOfAny, ищут первое вхождение любого значения в переданной коллекции. Новый тип System.Buffers.SearchValues<T> предназначен для передаче в такие методы. Соответственно, .NET 8 добавляет новые перегрузки методов, таких как MemoryExtensions.IndexOfAny, которые принимают экземпляр нового типа. При создании экземпляра SearchValues<T>все данные, необходимые для оптимизации последующих поисков, получены в этот момент, что означает, что работа выполняется заранее.
Новый тип System.Text.CompositeFormat полезен для оптимизации строк форматирования, которые не известны во время компиляции (например, если строка форматирования загружается из файла ресурса). Сначала тратится немного дополнительного времени на такие задачи, как разбор строки, но это экономит выполнение работы при каждом использовании.
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
Новые типы System.IO.Hashing.XxHash3 и System.IO.Hashing.XxHash128 обеспечивают реализацию быстрых алгоритмов хэширования XXH3 и XXH128.
System.Numerics и System.Runtime.Intrinsics
В этом разделе рассматриваются улучшения пространства имен System.Numerics и System.Runtime.Intrinsics.
-
Vector256<T>, Matrix3x2и Matrix4x4 улучшили аппаратное ускорение в .NET 8. Например, Vector256<T> был повторно реализован как внутренние операции
2x Vector128<T>
, где это возможно. Это позволяет частично ускорить некоторые функции приVector128.IsHardwareAccelerated == true
, ноVector256.IsHardwareAccelerated == false
, например в Arm64. - Встроенные компоненты оборудования теперь аннотированы с помощью атрибута
ConstExpected
. Это гарантирует, что пользователи знают, когда базовое оборудование ожидает константы и поэтому, когда неконстантное значение может неожиданно повредить производительность. - API Lerp(TSelf, TSelf, TSelf)
Lerp
добавлен в IFloatingPointIeee754<TSelf>,float
(Single),double
(Double) и Half. Этот API позволяет эффективно и правильно выполнять линейную интерполяцию между двумя значениями.
Vector512 и AVX-512
Поддержка SIMD в .NET Core 3.0 была расширена за счёт включения встроенных API аппаратных функций для конкретных платформ x86/x64. Поддержка для Arm64 была добавлена в .NET 5, а в .NET 7 были добавлены кроссплатформенные аппаратные примитивы. Платформа .NET 8 расширяет поддержку SIMD, внедрив Vector512<T> и добавив поддержку расширенных векторных инструкций Intel 512 (AVX-512).
В частности, .NET 8 включает поддержку следующих ключевых функций AVX-512:
- 512-разрядные операции векторов
- Дополнительные 16 регистров SIMD
- Дополнительные инструкции, доступные для 128-разрядных, 256-разрядных и 512-разрядных векторов
Если у вас есть оборудование, поддерживающее требуемую функциональность, Vector512.IsHardwareAccelerated теперь выводит true
.
.NET 8 также добавляет несколько платформо-зависимых классов в пространство имен System.Runtime.Intrinsics.X86.
- Avx512F (базовый)
- Avx512BW (байт и слово)
- Avx512CD (обнаружение конфликтов)
- Avx512DQ (двойное слово и четверное слово)
- Avx512Vbmi (инструкции по манипуляции векторными байтами)
Эти классы соответствуют той же общей форме, что и другие архитектуры наборов инструкций (ISAs), которые предоставляют свойство IsSupported и вложенный класс Avx512F.X64 для инструкций, доступных только для 64-разрядных процессов. Кроме того, каждый класс имеет вложенный Avx512F.VL класс, раскрывающий расширения Avx512VL
(векторная длина) для соответствующего набора инструкций.
Даже если вы явно не используете в коде инструкции, специфичные для Vector512
или Avx512F
, вы, скорее всего, всё равно сможете воспользоваться новой поддержкой AVX-512. JIT может использовать дополнительные регистры и инструкции неявно при использовании Vector128<T> или Vector256<T>. Библиотека базовых классов использует эти встроенные компоненты оборудования внутри большинства операций, предоставляемых Span<T> и ReadOnlySpan<T> и во многих математических API, предоставляемых для примитивных типов.
Проверка данных
Пространство имен System.ComponentModel.DataAnnotations включает новые атрибуты проверки данных, предназначенные для сценариев проверки в облачных службах. Хотя существующие валидаторы DataAnnotations
ориентированы на проверку типичных данных пользовательского интерфейса, таких как поля формы, новые атрибуты предназначены для проверки данных, не вводимых пользователем, таких как параметры конфигурации . В дополнение к новым атрибутам новые свойства были добавлены в тип RangeAttribute.
Новый API | Описание |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Указывает, включены ли границы в допустимый диапазон. |
System.ComponentModel.DataAnnotations.LengthAttribute | Задает как нижние, так и верхние границы для строк или коллекций. Например, [Length(10, 20)] требует не менее 10 элементов и не более 20 элементов в коллекции. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Проверяет, является ли строка допустимым представлением Base64. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Укажите списки разрешений и списки запретов соответственно. Например, [AllowedValues("apple", "banana", "mango")] . |
Метрика
Новые API позволяют присоединять теги пар "ключ-значение" к Meter и Instrument объектам при их создании. Агрегаторы опубликованных измерений метрик могут использовать теги для отличия агрегированных значений.
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);
К новым API относятся:
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
Криптография
.NET 8 добавляет поддержку примитивов хэширования SHA-3. (SHA-3 в настоящее время поддерживается в Linux с OpenSSL версии 1.1.1 или более поздней и в Windows 11 сборки 25324 или более поздней версии.) API, в которых доступен SHA-2, теперь предлагают дополнение SHA-3. Сюда входят SHA3_256
, SHA3_384
и SHA3_512
для хэширования; HMACSHA3_256
, HMACSHA3_384
и HMACSHA3_512
для HMAC; HashAlgorithmName.SHA3_256
, HashAlgorithmName.SHA3_384
и HashAlgorithmName.SHA3_512
для хэширования, где можно настроить алгоритм; и RSAEncryptionPadding.OaepSHA3_256
, RSAEncryptionPadding.OaepSHA3_384
и RSAEncryptionPadding.OaepSHA3_512
для шифрования RSA OAEP.
В следующем примере показано, как использовать API, включая свойство SHA3_256.IsSupported
, чтобы определить, поддерживает ли платформа 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
{
// ...
}
Поддержка SHA-3 в настоящее время направлена на поддержку криптографических примитивов. Более высокий уровень конструкций и протоколов, как ожидается, не будет полностью поддерживать SHA-3 изначально. К этим протоколам относятся сертификаты X.509, SignedXmlи COSE.
Сети
Поддержка прокси-сервера HTTPS
До сих пор все типы прокси-серверов, которые поддерживает HttpClient, позволяли атаке типа «человек посередине» видеть, к какому сайту подключается клиент, даже для URI HTTPS. HttpClient теперь поддерживает прокси-сервер HTTPS, который создает зашифрованный канал между клиентом и прокси-сервером, чтобы все запросы могли обрабатываться с полной конфиденциальностью.
Чтобы включить прокси-сервер HTTPS, задайте переменную среды all_proxy
или используйте класс WebProxy для программного управления прокси-сервером.
Unix: export all_proxy=https://x.x.x.x:3218
Windows: set all_proxy=https://x.x.x.x:3218
Можно также использовать класс WebProxy для программного управления прокси-сервером.
Методы ZipFile, использующие потоки
.NET 8 включает новые перегрузки ZipFile.CreateFromDirectory, которые позволяют собирать все файлы, содержащиеся в каталоге, упаковать их в архив, а затем сохранить полученный архив в предоставленный поток. Аналогичным образом новые перегрузки ZipFile.ExtractToDirectory дают возможность задать поток, содержащий зипованный файл, и извлечь его содержимое в файловую систему. Это новые перегрузки:
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) { }
}
Эти новые API могут быть полезны, если дисковое пространство ограничено, так как они не должны использовать диск в качестве промежуточного шага.
Библиотеки расширений
В этом разделе содержатся следующие подтопики:
- проверка параметров опций
- конструкторы LoggerMessageAttribute
- Метрики расширений
- размещенных служб жизненного цикла
- ключевые службы DI
- System.Numerics.Tensors.TensorPrimitives
Ключевые службы DI
Службы инъекции зависимостей (DI) с ключами позволяют регистрировать и получать службы DI по ключам. С помощью ключей можно определить, как зарегистрировать и использовать службы. Это некоторые из новых API:
- Интерфейс IKeyedServiceProvider.
- Атрибут ServiceKeyAttribute, который можно использовать для внедрения ключа, который использовался для регистрации или разрешения в конструкторе.
- Атрибут FromKeyedServicesAttribute, который можно использовать в параметрах конструктора служб, чтобы указать, какую службу с ключом использовать.
- Различные новые методы расширения для IServiceCollection для поддержки ключевых служб, например ServiceCollectionServiceExtensions.AddKeyedScoped.
- Реализация ServiceProviderIKeyedServiceProvider.
В следующем примере показано, как использовать службы DI с ключами.
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.";
}
Дополнительные сведения см. в разделе dotnet/runtime#64427.
Управляемые услуги жизненного цикла
Размещенные службы теперь имеют больше возможностей для выполнения во время жизненного цикла приложения.
IHostedService предоставил StartAsync
и StopAsync
, а теперь IHostedLifecycleService предоставляет следующие дополнительные методы:
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
Эти методы выполняются до и после существующих точек соответственно.
В следующем примере показано, как использовать новые 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;
}
}
Дополнительные сведения см. в dotnet/runtime#86511.
Проверка параметров
Генератор источника
Чтобы уменьшить затраты на запуск и улучшить набор функций проверки, мы представили генератор исходного кода, реализующий логику проверки. В следующем коде показаны примеры моделей и классов проверяющего элемента.
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>
{
}
Если приложение использует внедрение зависимостей, можно внедрить проверку, как показано в следующем примере кода.
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>();
Тип ValidateOptionsResultBuilder
.NET 8 представляет тип ValidateOptionsResultBuilder для упрощения создания объекта ValidateOptionsResult. Важно, что этот конструктор позволяет накапливать множество ошибок. Ранее создание объекта ValidateOptionsResult, необходимого для реализации IValidateOptions<TOptions>.Validate(String, TOptions), было сложным и иногда приводило к ошибкам многоуровневой проверки. При наличии нескольких ошибок процесс проверки часто останавливается при первой ошибке.
В следующем фрагменте кода показан пример использования 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();
Конструкторы LoggerMessageAttribute
LoggerMessageAttribute теперь предлагает дополнительные перегрузки конструктора. Ранее необходимо выбрать конструктор без параметров или конструктор, который требовал все параметры (идентификатор события, уровень журнала и сообщение). Новые перегрузки обеспечивают большую гибкость при указании требуемых параметров при меньшем объеме кода. Если вы не предоставляете идентификатор события, система автоматически создает его.
public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);
Метрики расширений
Интерфейс IMeterFactory
Вы можете зарегистрировать новый интерфейс IMeterFactory в контейнерах внедрения зависимостей (DI) и использовать его для создания объектов Meter изолированным образом.
Зарегистрируйте IMeterFactory в DI-контейнере, используя стандартную реализацию фабрики метрических счетчиков.
// 'services' is the DI IServiceCollection.
services.AddMetrics();
Затем потребители могут получить фабрику счётчиков и использовать её для создания нового объекта Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Класс T> MetricCollector<
Новый класс MetricCollector<T> позволяет записывать измерения метрик вместе с метками времени. Кроме того, класс предлагает гибкость использования поставщика времени для точного создания метки времени.
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
Обновленный пакет NuGet System.Numerics.Tensors включает API для нового типа System.Numerics.Tensors.TensorPrimitives, который добавляет поддержку тензорных операций. Тензорные примитивы оптимизируют рабочие нагрузки с большим объемом данных, такие как ИИ и машинное обучение.
Нагрузки искусственного интеллекта, такие как семантический поиск и генерация с дополнением извлеченных данных (RAG), дополняют возможности естественного языка крупных языковых моделей, таких как ChatGPT, добавляя в запросы релевантные данные. Для этих рабочих нагрузок операции с векторами (например, косинусное сходство), чтобы найти наиболее релевантные данные для ответа на вопрос, являются важными. Тип TensorPrimitives предоставляет API для операций векторов.
Дополнительную информацию см. в публикации блога "Анонс .NET 8 RC 2".
Поддержка встроенного AOT
Впервые в .NET 7 появилась возможность публикации в качестве собственного AOT. Публикация приложения с помощью Native AOT создает полностью автономную версию приложения, которая не требует среды выполнения, все включено в один файл. .NET 8 включает следующие улучшения в публикации встроенного AOT:
Добавляет поддержку архитектур x64 и Arm64 в macOS.
Уменьшает размер нативных AOT приложений в Linux до 50 процентов%. В следующей таблице показан размер приложения Hello World, опубликованного в Native AOT, который включает всю среду выполнения .NET в .NET 7 и .NET 8:
Операционная система .NET 7 .NET 8 Linux x64 (с -p:StripSymbols=true
)3,76 МБ 1,84 МБ Windows x64 2,85 МБ 1,77 МБ Позволяет указать предпочтения оптимизации: размер или скорость. По умолчанию компилятор выбирает создание быстрого кода при учете размера приложения. Однако вы можете использовать свойство MSBuild
<OptimizationPreference>
для оптимизации конкретно для одного или другого. Дополнительные сведения см. в статье Оптимизация развертываний AOT.
Ориентируйтесь на iOS-подобные платформы с помощью технологии Native AOT.
.NET 8 запускает работу, чтобы включить поддержку машинного AOT для платформ iOS. Теперь вы можете создавать и запускать приложения .NET iOS и .NET MAUI с помощью Native AOT на следующих платформах:
ios
iossimulator
maccatalyst
tvos
tvossimulator
Предварительное тестирование показывает, что размер приложения на диске уменьшается примерно на 35% для приложений iOS .NET, использующих собственный AOT вместо Mono. Размер приложения на диске для приложений iOS для .NET MAUI уменьшается до 50%. Кроме того, время запуска также быстрее. Приложения .NET iOS запускаются примерно на 28% быстрее, в то время как приложения .NET MAUI iOS показывают улучшенную производительность запуска примерно на 50% по сравнению с Mono. Поддержка .NET 8 является экспериментальной и только первым шагом для функции в целом. Дополнительные сведения см. в публикации блога .NET MAUI об улучшениях производительности в .NET 8 .
Поддержка нативного AOT доступна как опциональная функция, предназначенная для развертывания приложений; по-прежнему Mono является средой выполнения по умолчанию для разработки и развертывания приложений. Чтобы создать и запустить приложение .NET MAUI с помощью Native AOT на устройстве iOS, используйте dotnet workload install maui
для установки рабочей нагрузки .NET MAUI и dotnet new maui -n HelloMaui
для создания приложения. Затем задайте для свойства MSBuild значение PublishAot
true
в файле проекта.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Если задать необходимое свойство и запустить dotnet publish
, как показано в следующем примере, приложение будет развернуто с использованием Native AOT.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Ограничения
Не все возможности iOS совместимы с родным AOT. Аналогичным образом, не все библиотеки, часто используемые в iOS, совместимы с NativeAOT. В дополнение к существующим ограничениям развертывания Машинного развертывания AOT, в следующем списке показаны некоторые из других ограничений при выборе платформ, таких как iOS:
- Использование нативного AOT включено только во время развертывания приложения (
dotnet publish
). - Отладка управляемого кода поддерживается только в Mono.
- Совместимость с платформой .NET MAUI ограничена.
Компиляция AOT для приложений Android
Чтобы уменьшить размер приложений, приложения .NET и .NET MAUI, предназначенные для Android, используют профилируемые режиме компиляции (AOT), когда они встроены в режим выпуска. Профилированная компиляция AOT влияет на меньше методов, чем обычная компиляция AOT. В .NET 8 представлено свойство <AndroidStripILAfterAOT>
, которое позволяет использовать дополнительную компиляцию AOT для приложений Android, чтобы уменьшить размер приложения еще больше.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
По умолчанию параметр AndroidStripILAfterAOT
для true
переопределяет параметр AndroidEnableProfiledAot
по умолчанию, что позволяет (почти) обрезать все методы, скомпилированные AOT. Кроме того, можно использовать профилированный AOT и удаление IL вместе, явно задав оба свойства для true
.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
Кросс-платформенные приложения для Windows
При создании приложений, предназначенных для Windows на платформах, отличных от Windows, результирующий исполняемый файл теперь обновляется с помощью всех указанных ресурсов Win32, например значка приложения, манифеста, сведений о версии.
Ранее приложения должны создаваться в Windows, чтобы иметь такие ресурсы. Устранение этого разрыва в поддержке кросс-компиляции было популярным запросом, так как это была значительная проблема, влияющая как на сложность инфраструктуры, так и на использование ресурсов.