Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описываются новые возможности библиотек .NET для .NET 9.
База64Url
Base64 — это схема кодирования, которая преобразует произвольные байты в текст, состоящий из определенного набора 64 символов. Это общий подход для передачи данных и уже давно поддерживается с помощью различных методов, таких как или Convert.ToBase64StringBase64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Однако некоторые символы, используемые в нем, делают его менее идеальным для использования в некоторых обстоятельствах, например в строках запроса. В частности, 64 символа, составляющих таблицу Base64, включают "+" и "/", оба из которых имеют собственное значение в URL-адресах. Это привело к созданию схемы Base64Url, которая похожа на Base64, но использует немного другой набор символов, который делает его подходящим для использования в контекстах URL-адресов. .NET 9 включает новый Base64Url класс, который предоставляет множество полезных и оптимизированных методов для кодирования и декодирования с Base64Url
различными типами данных.
В следующем примере показано использование нового класса.
ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);
Двоичный форматтер
.NET 9 удаляется BinaryFormatter из среды выполнения .NET. API-интерфейсы по-прежнему присутствуют, но их реализации всегда вызывают исключение независимо от типа проекта. Дополнительные сведения об удалении и вариантах, если вы затронуты, см. в руководстве по миграции BinaryFormatter.
Коллекции
Типы коллекций в .NET получают следующие обновления для .NET 9:
- Поиски коллекции с диапазонами
OrderedDictionary<TKey, TValue>
- Метод PriorityQueue.Remove() позволяет обновить приоритет элемента в очереди.
ReadOnlySet<T>
Поиски коллекции с диапазонами
В коде высокой производительности диапазоны часто используются, чтобы избежать ненужных выделений строк, а таблицы подстановки с типами, как Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве кэшей. Однако не было безопасного встроенного механизма поиска в этих типах коллекций с диапазонами. Благодаря новой функции в C# 13 и новых allows ref struct
функциях этих типов коллекций в .NET 9 теперь можно выполнять такие виды подстановок.
В следующем примере показано использование словаря<TKey,TValue>. GetAlternateLookup.
static Dictionary<string, int> CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> wordCounts = new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
wordCounts.GetAlternateLookup<ReadOnlySpan<char>>();
foreach (Range wordRange in Regex.EnumerateSplits(input, @"\b\W+"))
{
if (wordRange.Start.Value == wordRange.End.Value)
{
continue; // Skip empty ranges.
}
ReadOnlySpan<char> word = input[wordRange];
spanLookup[word] = spanLookup.TryGetValue(word, out int count) ? count + 1 : 1;
}
return wordCounts;
}
OrderedDictionary<TKey, TValue>
Во многих сценариях может потребоваться хранить пары "ключ-значение" таким образом, чтобы порядок мог поддерживаться (список пар "ключ-значение"), но при быстром поиске по ключу также поддерживается (словарь пар "ключ-значение"). С первых дней .NET OrderedDictionary тип поддерживает этот сценарий, но только не универсальным образом, с ключами и значениями, типизированными как object
. .NET 9 представляет длинную OrderedDictionary<TKey,TValue> запрошенную коллекцию, которая обеспечивает эффективный универсальный тип для поддержки этих сценариев.
В следующем коде используется новый класс.
OrderedDictionary<string, int> d = new()
{
["a"] = 1,
["b"] = 2,
["c"] = 3,
};
d.Add("d", 4);
d.RemoveAt(0);
d.RemoveAt(2);
d.Insert(0, "e", 5);
foreach (KeyValuePair<string, int> entry in d)
{
Console.WriteLine(entry);
}
// Output:
// [e, 5]
// [b, 2]
// [c, 3]
Метод PriorityQueue.Remove()
.NET 6 представила коллекцию PriorityQueue<TElement,TPriority> , которая обеспечивает простую и быструю реализацию массива-кучи. Одна из проблем с кучами массивов в целом заключается в том, что они не поддерживают обновления приоритета, что делает их запрещенными для использования в алгоритмах, таких как вариации алгоритма Dijkstra.
Хотя невозможно реализовать эффективные обновления приоритета $O(\log n)$ в существующей коллекции, новый PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) метод позволяет эмулировать обновления приоритета (хотя и в $O(n)$ времени):
public static void UpdatePriority<TElement, TPriority>(
this PriorityQueue<TElement, TPriority> queue,
TElement element,
TPriority priority
)
{
// Scan the heap for entries matching the current element.
queue.Remove(element, out _, out _);
// Re-insert the entry with the new priority.
queue.Enqueue(element, priority);
}
Этот метод разблокирует пользователей, которые хотят реализовать алгоритмы графа в контекстах, где асимптотическая производительность не является блокировщиком. (Такие контексты включают образование и прототип.) Например, вот реализация алгоритма Dijkstra , использующего новый API.
ReadOnlySet<T>
Часто желательно предоставлять доступные только для чтения представления коллекций. ReadOnlyCollection<T> позволяет создать оболочку только для чтения вокруг произвольного изменяемого IList<T>объекта и ReadOnlyDictionary<TKey,TValue> позволяет создать оболочку только для чтения вокруг произвольного изменяемого IDictionary<TKey,TValue>. Однако в предыдущих версиях .NET нет встроенной поддержки для выполнения этого же действия ISet<T>. .NET 9 содержит сведения ReadOnlySet<T> об этом.
Новый класс включает следующий шаблон использования.
private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;
public ReadOnlySet<int> Set => _setWrapper ??= new(_set);
Модель компонента — TypeDescriptor
поддержка обрезки
System.ComponentModel включает новые API, совместимые с триммером, для описания компонентов. Любое приложение, особенно автономное обрезка приложений, может использовать эти новые API для поддержки сценариев обрезки.
Основной API — это TypeDescriptor.RegisterType метод класса TypeDescriptor
. Этот метод имеет DynamicallyAccessedMembersAttribute атрибут, чтобы триммер сохранял элементы для этого типа. Этот метод следует вызывать один раз на тип и, как правило, рано.
Вторичные API имеют FromRegisteredType
суффикс, например TypeDescriptor.GetPropertiesFromRegisteredType(Type). В отличие от своих коллег, у которых нет FromRegisteredType
суффикса, эти API не имеют [RequiresUnreferencedCode]
или [DynamicallyAccessedMembers]
триммерных атрибутов. Отсутствие атрибутов триммера помогает потребителям больше не иметь:
- Подавление предупреждений обрезки, которые могут быть рискованными.
- Распространение строго типизированного
Type
параметра на другие методы, которые могут быть громоздкими или неуловимыми.
public static void RunIt()
{
// The Type from typeof() is passed to a different method.
// The trimmer doesn't know about ExampleClass anymore
// and thus there will be warnings when trimming.
Test(typeof(ExampleClass));
Console.ReadLine();
}
private static void Test(Type type)
{
// When publishing self-contained + trimmed,
// this line produces warnings IL2026 and IL2067.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(type);
// When publishing self-contained + trimmed,
// the property count is 0 here instead of 2.
Console.WriteLine($"Property count: {properties.Count}");
// To avoid the warning and ensure reflection
// can see the properties, register the type:
TypeDescriptor.RegisterType<ExampleClass>();
// Get properties from the registered type.
properties = TypeDescriptor.GetPropertiesFromRegisteredType(type);
Console.WriteLine($"Property count: {properties.Count}");
}
public class ExampleClass
{
public string? Property1 { get; set; }
public int Property2 { get; set; }
}
Дополнительные сведения см. в предложении API .
Криптография
- Метод CryptographicOperations.HashData()
- Алгоритм KMAC
- алгоритмыAES-GCM и ChaChaPoly1305, включенные для iOS/tvOS/MacCatalyst
- Загрузка сертификата X.509
- Поддержка поставщиков OpenSSL
- Безопасность на основе виртуализации Windows CNG
Метод CryptographicOperations.HashData()
.NET включает несколько статических реализаций хэш-функций и связанных функций. Эти API включают SHA256.HashData и HMACSHA256.HashData. Api с одним снимком предпочтительнее использовать, так как они могут обеспечить оптимальную производительность и уменьшить или исключить выделение.
Если разработчик хочет предоставить API, поддерживающий хэширование, где вызывающий определяет используемый хэш-алгоритм, он обычно выполняется путем принятия аргумента HashAlgorithmName . Однако использование этого шаблона с интерфейсами API с одним снимком потребует переключения на все возможные HashAlgorithmName , а затем с помощью соответствующего метода. Чтобы решить эту проблему, .NET 9 представляет CryptographicOperations.HashData API. Этот API позволяет создавать хэш или HMAC через входные данные в виде одного снимка, где используемый алгоритм определяется HashAlgorithmName.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
Алгоритм KMAC
.NET 9 предоставляет алгоритм KMAC, указанный NIST SP-800-185. Код проверки подлинности сообщений KECCAK (KMAC) — это псевдорандомная функция и хэш-функция с ключом на основе KECCAK.
Следующие новые классы используют алгоритм KMAC. Используйте экземпляры для аккумулирования данных для создания MAC или использования статического HashData
метода для однократного ввода .
KMAC доступен в Linux с OpenSSL 3.0 или более поздней версии, а также в Windows 11 сборки 26016 или более поздней версии. Вы можете использовать статическое IsSupported
свойство, чтобы определить, поддерживает ли платформа нужный алгоритм.
if (Kmac128.IsSupported)
{
byte[] key = GetKmacKey();
byte[] input = GetInputToMac();
byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
// Handle scenario where KMAC isn't available.
}
алгоритмы AES-GCM и ChaChaPoly1305, включенные для iOS/tvOS/MacCatalyst
IsSupported и ChaChaPoly1305.IsSupported
теперь возвращает значение true при запуске в iOS 13+, tvOS 13+ и Mac Catalyst.
AesGcm Поддерживает только 16-байтовые (128-разрядные) значения тегов в операционных системах Apple.
Загрузка сертификата X.509
С .NET Framework 2.0 способ загрузки сертификата был new X509Certificate2(bytes)
. Существуют и другие шаблоны, такие как new X509Certificate2(bytes, password, flags)
, new X509Certificate2(path)
new X509Certificate2(path, password, flags)
и (и X509Certificate2Collection.Import(bytes, password, flags)
ее перегрузки).
Эти методы все используемые фрагменты содержимого используются для определения того, что входные данные могут обрабатываться, а затем загружали его, если это возможно. Для некоторых абонентов эта стратегия была очень удобной. Но у него также есть некоторые проблемы:
- Не все форматы файлов работают в каждой ОС.
- Это отклонение протокола.
- Это источник проблем безопасности.
.NET 9 представляет новый X509CertificateLoader класс, который имеет проект "один метод, одна цель". В исходной версии он поддерживает только два из пяти форматов, поддерживаемых конструктором X509Certificate2 . Это два формата, которые работали над всеми операционными системами.
Поддержка поставщиков OpenSSL
.NET 8 представил API-интерфейсы OpenPrivateKeyFromEngine(String, String) OpenSSL и OpenPublicKeyFromEngine(String, String). Они позволяют взаимодействовать с компонентами OpenSSL ENGINE
и использовать аппаратные модули безопасности (HSM), например.
В .NET 9 представлено, что позволяет использовать поставщиков SafeEvpPKeyHandle.OpenKeyFromProvider(String, String)OpenSSL и взаимодействовать с такими поставщиками, как tpm2
илиpkcs11
.
Некоторые дистрибутивы удалили ENGINE
поддержку , так как теперь она устарела.
В следующем фрагменте кода показано базовое использование:
byte[] data = [ /* example data */ ];
// Refer to your provider documentation, for example, https://github.com/tpm2-software/tpm2-openssl/tree/master.
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2", "handle:0x81000007"))
using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle))
{
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
// Do stuff with signature created by TPM.
}
Во время подтверждения TLS есть некоторые улучшения производительности, а также улучшения взаимодействия с закрытыми ключами RSA, которые используют ENGINE
компоненты.
Безопасность на основе виртуализации Windows CNG
Windows 11 добавил новые API для защиты ключей Windows с помощью безопасности на основе виртуализации (VBS). Благодаря этой новой возможности ключи можно защитить от атак ключей уровня администратора с незначительным эффектом на производительность, надежность или масштабирование.
.NET 9 добавил соответствующие CngKeyCreationOptions флаги. Добавлены следующие три флага:
-
CngKeyCreationOptions.PreferVbs
сопоставлениеNCRYPT_PREFER_VBS_FLAG
-
CngKeyCreationOptions.RequireVbs
сопоставлениеNCRYPT_REQUIRE_VBS_FLAG
-
CngKeyCreationOptions.UsePerBootKey
сопоставлениеNCRYPT_USE_PER_BOOT_KEY_FLAG
В следующем фрагменте показано, как использовать один из флагов:
using System.Security.Cryptography;
CngKeyCreationParameters cngCreationParams = new()
{
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
KeyCreationOptions = CngKeyCreationOptions.RequireVbs | CngKeyCreationOptions.OverwriteExistingKey,
};
using (CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256, "myKey", cngCreationParams))
using (ECDsaCng ecdsa = new ECDsaCng(key))
{
// Do stuff with the key.
}
Дата и время — новые перегрузки TimeSpan.From*
Класс TimeSpan предлагает несколько From*
методов, которые позволяют создать TimeSpan
объект с помощью double
объекта. Тем не менее, так как double
это формат с плавающей запятой на основе двоичного кода, неустранимая нерекомендация может привести к ошибкам. Например, TimeSpan.FromSeconds(101.832)
может не точно представлять 101 seconds, 832 milliseconds
, а приблизительно 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds
. Это несоответствие вызвало частое путаницу, и это также не самый эффективный способ представления таких данных. Для решения этой проблемы .NET 9 добавляет новые перегрузки, которые позволяют создавать TimeSpan
объекты из целых чисел. Существуют новые перегрузки из FromDays
, FromHours
, FromMinutes
, FromSeconds
, и FromMilliseconds
FromMicroseconds
.
В следующем коде показан пример вызова double
и одной из новых целых перегружений.
TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832);
Console.WriteLine($"timeSpan1 = {timeSpan1}");
// timeSpan1 = 00:01:41.8319999
TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);
Console.WriteLine($"timeSpan2 = {timeSpan2}");
// timeSpan2 = 00:01:41.8320000
Внедрение зависимостей — ActivatorUtilities.CreateInstance
конструктор
Разрешение ActivatorUtilities.CreateInstance конструктора изменилось в .NET 9. Ранее конструктор, который был явно помечен с помощью ActivatorUtilitiesConstructorAttribute атрибута, может не вызываться в зависимости от порядка конструкторов и количества параметров конструктора. Логика изменилась в .NET 9, так что конструктор, имеющий атрибут, всегда вызывается.
Диагностика
- Debug.Assert сообщает о условии утверждения по умолчанию
- Новый метод Activity.AddLink
- Инструмент Metrics.Gauge
- Прослушивание подстановочных знаков с подстановочными знаками без учета
Debug.Assert сообщает о условии утверждения по умолчанию
Debug.Assert обычно используется для проверки условий, которые должны всегда быть истинными. Сбой обычно указывает на ошибку в коде. Существует много перегрузок Debug.Assert, простейший из которых только принимает условие:
Debug.Assert(a > 0 && b > 0);
Утверждение завершается ошибкой, если условие равно false. Исторически, однако, такие утверждения были пустыми из каких-либо сведений о том, какое условие завершилось сбоем. Начиная с .NET 9, если сообщение не предоставляется пользователем явным образом, утверждение будет содержать текстовое представление условия. Например, для предыдущего примера утверждения вместо получения сообщения, например:
Process terminated. Assertion failed.
at Program.SomeMethod(Int32 a, Int32 b)
Теперь сообщение будет следующим:
Process terminated. Assertion failed.
a > 0 && b > 0
at Program.SomeMethod(Int32 a, Int32 b)
Новый метод Activity.AddLink
Ранее при Activityтрассировки можно связать трассировку Activity
только с другими контекстами трассировки. Новый в .NET 9 AddLink(ActivityLink) API позволяет связать Activity
объект с другими контекстами трассировки после его создания. Это изменение также соответствует спецификациям OpenTelemetry .
ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);
Activity activity = new("LinkTest");
activity.AddLink(activityLink);
Инструмент Metrics.Gauge
System.Diagnostics.Metrics теперь предоставляет Gauge<T> инструмент в соответствии со спецификацией OpenTelemetry. Инструмент Gauge
предназначен для записи неадитивных значений при возникновении изменений. Например, он может измерять фоновый уровень шума, где суммирование значений из нескольких комнат будет нечувствичным. Инструмент Gauge
— это универсальный тип, который может записывать любой тип значения, например int
, double
или decimal
.
В следующем примере показано использование Gauge
инструмента.
Meter soundMeter = new("MeasurementLibrary.Sound");
Gauge<int> gauge = soundMeter.CreateGauge<int>(
name: "NoiseLevel",
unit: "dB", // Decibels.
description: "Background Noise Level"
);
gauge.Record(10, new TagList() { { "Room1", "dB" } });
Прослушивание подстановочных знаков с подстановочными знаками без учета
Вы уже можете прослушивать метрики вне процесса с помощью System.Diagnostics.Metrics поставщика источника событий, но до .NET 9 необходимо было указать полное имя счетчика. В .NET 9 можно прослушивать все метрики с помощью подстановочного знака *
, который позволяет записывать метрики из каждого счетчика в процессе. Кроме того, он добавляет поддержку прослушивания префиксом счетчика, чтобы вы могли прослушивать все метры, имена которых начинаются с указанного префикса. Например, указание MyMeter*
позволяет прослушивать все метры с именами, начинающимися с MyMeter
.
// The complete meter name is "MyCompany.MyMeter".
var meter = new Meter("MyCompany.MyMeter");
// Create a counter and allow publishing values.
meter.CreateObservableCounter("MyCounter", () => 1);
// Create the listener to use the wildcard character
// to listen to all meters using prefix names.
MyEventListener listener = new MyEventListener();
Класс MyEventListener
определяется следующим образом.
internal class MyEventListener : EventListener
{
protected override void OnEventSourceCreated(EventSource eventSource)
{
Console.WriteLine(eventSource.Name);
if (eventSource.Name == "System.Diagnostics.Metrics")
{
// Listen to all meters with names starting with "MyCompany".
// If using "*", allow listening to all meters.
EnableEvents(
eventSource,
EventLevel.Informational,
(EventKeywords)0x3,
new Dictionary<string, string?>() { { "Metrics", "MyCompany*" } }
);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// Ignore other events.
if (eventData.EventSource.Name != "System.Diagnostics.Metrics" ||
eventData.EventName == "CollectionStart" ||
eventData.EventName == "CollectionStop" ||
eventData.EventName == "InstrumentPublished"
)
return;
Console.WriteLine(eventData.EventName);
if (eventData.Payload is not null)
{
for (int i = 0; i < eventData.Payload.Count; i++)
Console.WriteLine($"\t{eventData.PayloadNames![i]}: {eventData.Payload[i]}");
}
}
}
При выполнении кода выходные данные приведены следующим образом:
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
Можно также использовать подстановочный знак для прослушивания метрик с помощью таких средств мониторинга, как dotnet-counters.
LINQ
Появились новые методы CountBy и AggregateBy были введены. Эти методы позволяют агрегировать состояние по ключу без необходимости выделения промежуточных групп с помощью GroupBy.
CountBy позволяет быстро вычислять частоту каждого ключа. Следующий пример находит слово, которое чаще всего встречается в текстовой строке.
string sourceText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed non risus. Suspendisse lectus tortor, dignissim sit amet,
adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";
// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(word => word.ToLowerInvariant())
.CountBy(word => word)
.MaxBy(pair => pair.Value);
Console.WriteLine(mostFrequentWord.Key); // amet
AggregateBy позволяет реализовать более общие рабочие процессы. В следующем примере показано, как вычислить оценки, связанные с заданным ключом.
(string id, int score)[] data =
[
("0", 42),
("1", 5),
("2", 4),
("1", 10),
("0", 25),
];
var aggregatedData =
data.AggregateBy(
keySelector: entry => entry.id,
seed: 0,
(totalScore, curr) => totalScore + curr.score
);
foreach (var item in aggregatedData)
{
Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)
Index<TSource>(IEnumerable<TSource>) позволяет быстро извлечь неявный индекс перечисленного. Теперь можно написать код, например следующий фрагмент кода, чтобы автоматически индексировать элементы в коллекции.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Генератор источника ведения журнала
В C# 12 появились первичные конструкторы, которые позволяют определить конструктор непосредственно в объявлении класса. Генератор источников ведения журнала теперь поддерживает ведение журнала с помощью классов, имеющих основной конструктор.
public partial class ClassWithPrimaryConstructor(ILogger logger)
{
[LoggerMessage(0, LogLevel.Debug, "Test.")]
public partial void Test();
}
Разное
В этом разделе приведены сведения о следующем:
allows ref struct
используется в библиотеках
C# 13 представляет возможность ограничить универсальный параметр allows ref struct
, который сообщает компилятору и среде выполнения, что ref struct
может использоваться для этого универсального параметра. Многие API, совместимые с этим интерфейсом, теперь были замечены. Например, метод String.Create имеет перегрузку, которая позволяет создавать string
, записывая непосредственно в память, представленную как диапазон. Этот метод имеет аргумент, передаваемый TState
из вызывающего объекта в делегат, который выполняет фактическую запись.
Этот TState
параметр String.Create
типа теперь аннотирован следующим allows ref struct
образом:
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
where TState : allows ref struct;
Эта заметка позволяет передавать диапазон (или любой другой ref struct
) в качестве входных данных в этот метод.
В следующем примере показана новая String.ToLowerInvariant() перегрузка, которая использует эту возможность.
public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));
SearchValues
расширение
.NET 8 представил SearchValues<T> тип, который предоставляет оптимизированное решение для поиска определенных наборов символов или байтов в пределах диапазона. В .NET 9 SearchValues
была расширена поддержка поиска подстроок в более крупной строке.
В следующем примере выполняется поиск нескольких имен животных в строковом значении и возвращается индекс первой найденной.
private static readonly SearchValues<string> s_animals =
SearchValues.Create(["cat", "mouse", "dog", "dolphin"], StringComparison.OrdinalIgnoreCase);
public static int IndexOfAnimal(string text) =>
text.AsSpan().IndexOfAny(s_animals);
Эта новая возможность имеет оптимизированную реализацию, которая использует поддержку SIMD на базовой платформе. Кроме того, он позволяет оптимизировать типы более высокого уровня. Например, Regex теперь эта функция используется как часть ее реализации.
Нетворкинг
- SocketsHttpHandler по умолчанию используется в HttpClientFactory
- System.Net.ServerSentEvents (СобытияSystem.Net.ServerSentEvents)
- Возобновление TLS с сертификатами клиента в Linux
- WebSocket сохраняет связь и время ожидания
- HttpClientFactory больше не регистрирует значения заголовков по умолчанию
SocketsHttpHandler по умолчанию используется в HttpClientFactory
HttpClientFactory
создает HttpClient объекты, поддерживаемые HttpClientHandlerпо умолчанию.
HttpClientHandler
она поддерживается SocketsHttpHandlerгораздо более настраиваемой, включая управление временем существования подключения.
HttpClientFactory
теперь используется SocketsHttpHandler
по умолчанию и настраивает его, чтобы задать ограничения времени существования подключения, соответствующие времени существования поворота, указанному в фабрике.
System.Net.ServerSentEvents (СобытияSystem.Net.ServerSentEvents)
События, отправленные сервером (SSE), — это простой и популярный протокол для потоковой передачи данных с сервера на клиент. Он используется, например, OpenAI в рамках потоковой передачи созданного текста из служб ИИ. Чтобы упростить использование SSE, новая System.Net.ServerSentEvents библиотека предоставляет средство синтаксического анализа для легкого приема событий, отправленных сервером.
Следующий код демонстрирует использование нового класса.
Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
Console.WriteLine(e.Data);
}
Возобновление TLS с сертификатами клиента в Linux
Возобновление TLS — это функция протокола TLS, который позволяет возобновить ранее установленные сеансы на сервере. Это позволяет избежать нескольких циклов и сохраняет вычислительные ресурсы во время подтверждения TLS.
Возобновление TLS уже поддерживается в Linux для подключений SslStream без сертификатов клиента. .NET 9 добавляет поддержку возобновления TLS взаимно прошедших проверку подлинности TLS-подключений, которые часто используются в сценариях "сервер — сервер". Функция включена автоматически.
WebSocket сохраняет связь и время ожидания
Новые API ClientWebSocketOptions и WebSocketCreationOptions позволяют вам отказаться от отправки WebSocket ping и прерывания подключения, если одноранговый узел не отвечает вовремя.
До сих пор можно указать KeepAliveInterval , что подключение остается бездействующим, но не было встроенного механизма для принудительного применения ответа однорангового узла.
В следующем примере сервер отправляет запросы каждые 5 секунд и прерывает подключение, если оно не отвечает в течение секунды.
using var cws = new ClientWebSocket();
cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
cws.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
cws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(1);
await cws.ConnectAsync(uri, httpClient, cancellationToken);
HttpClientFactory больше не регистрирует значения заголовков по умолчанию
LogLevel.Trace События, зарегистрированные HttpClientFactory
по умолчанию, не включают значения заголовков. Вы можете выбрать ведение журнала значений для определенных заголовков с помощью вспомогательного RedactLoggedHeaders метода.
Следующий пример редактирует все заголовки, за исключением агента пользователя.
services.AddHttpClient("myClient")
.RedactLoggedHeaders(name => name != "User-Agent");
Дополнительные сведения см. в статье HttpClientFactory, где по умолчанию отображаются значения заголовков redacty.
Отражение
Сохраненные сборки
В версиях .NET Core и .NET 5-8 поддержка создания сборки и создания метаданных отражения для динамически созданных типов была ограничена выполнением AssemblyBuilder. Отсутствие поддержки сохранения сборки часто является блокировщиком для клиентов, переносящихся с .NET Framework на .NET. .NET 9 добавляет новый тип, PersistedAssemblyBuilderкоторый можно использовать для сохранения создаваемой сборки.
Чтобы создать PersistedAssemblyBuilder
экземпляр, вызовите конструктор и передайте имя сборки, основную сборку, System.Private.CoreLib
для ссылки на базовые типы среды выполнения и необязательные настраиваемые атрибуты. После отправки всех членов в сборку вызовите PersistedAssemblyBuilder.Save(String) метод для создания сборки с параметрами по умолчанию. Если вы хотите задать точку входа или другие параметры, можно вызвать PersistedAssemblyBuilder.GenerateMetadata и использовать метаданные, которые он возвращает для сохранения сборки. В следующем коде показан пример создания сохраненной сборки и задания точки входа.
public void CreateAndSaveAssembly(string assemblyPath)
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(
new AssemblyName("MyAssembly"),
typeof(object).Assembly
);
TypeBuilder tb = ab.DefineDynamicModule("MyModule")
.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder entryPoint = tb.DefineMethod(
"Main",
MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static
);
ILGenerator il = entryPoint.GetILGenerator();
// ...
il.Emit(OpCodes.Ret);
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(
out BlobBuilder ilStream,
out BlobBuilder fieldData
);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(
imageCharacteristics: Characteristics.ExecutableImage);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: peHeaderBuilder,
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)
);
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
public static void UseAssembly(string assemblyPath)
{
Assembly assembly = Assembly.LoadFrom(assemblyPath);
Type? type = assembly.GetType("MyType");
MethodInfo? method = type?.GetMethod("SumMethod");
Console.WriteLine(method?.Invoke(null, [5, 10]));
}
Новый PersistedAssemblyBuilder класс включает поддержку PDB. Вы можете выдавать сведения о символах и использовать его для отладки созданной сборки. API имеет аналогичную форму реализации .NET Framework. Дополнительные сведения см. в разделе "Выдача символов" и создание PDB.
Синтаксический анализ имени типа
TypeName — это средство синтаксического анализа имен типов ECMA-335, которое предоставляет те же функции, что System.Type и среда выполнения. Компоненты, такие как сериализаторы и компиляторы, должны анализировать и обрабатывать имена типов. Например, собственный компилятор AOT переключился на использование TypeName.
Новый TypeName
класс предоставляет следующие возможности:
Статические
Parse
иTryParse
методы для синтаксического анализа входных данных, представленных какReadOnlySpan<char>
. Оба метода принимают экземплярTypeNameParseOptions
класса (пакет параметров), который позволяет настроить синтаксический анализ.Name
,FullName
иAssemblyQualifiedName
свойства, которые работают точно так же, как их коллеги в System.Type.Несколько свойств и методов, которые предоставляют дополнительные сведения о самом имени:
-
IsArray
,IsSZArray
(SZ
обозначает одномерный, нумерованный массив),IsVariableBoundArrayType
а такжеGetArrayRank
для работы с массивами. -
IsConstructedGenericType
,GetGenericTypeDefinition
а такжеGetGenericArguments
для работы с именами универсальных типов. -
IsByRef
а такжеIsPointer
для работы с указателями и управляемыми ссылками. -
GetElementType()
для работы с указателями, ссылками и массивами. -
IsNested
иDeclaringType
для работы с вложенными типами. -
AssemblyName
, который предоставляет сведения об имени сборки через новый AssemblyNameInfo класс. В отличие отAssemblyName
этого, новый тип неизменяем, а синтаксический анализ имен языка и региональных параметров не создает экземплярыCultureInfo
.
-
Оба TypeName
типа AssemblyNameInfo
неизменяемы и не предоставляют способ проверки равенства (они не реализуются IEquatable
). Сравнение имен сборок является простым, но в разных сценариях необходимо сравнить только подмножество предоставленных сведений (Name
, , Version
и CultureName
PublicKeyOrToken
).
В следующем фрагменте кода показан пример использования.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
internal class RestrictedSerializationBinder
{
Dictionary<string, Type> AllowList { get; set; }
RestrictedSerializationBinder(Type[] allowedTypes)
=> AllowList = allowedTypes.ToDictionary(type => type.FullName!);
Type? GetType(ReadOnlySpan<char> untrustedInput)
{
if (!TypeName.TryParse(untrustedInput, out TypeName? parsed))
{
throw new InvalidOperationException($"Invalid type name: '{untrustedInput.ToString()}'");
}
if (AllowList.TryGetValue(parsed.FullName, out Type? type))
{
return type;
}
else if (parsed.IsSimple // It's not generic, pointer, reference, or an array.
&& parsed.AssemblyName is not null
&& parsed.AssemblyName.Name == "MyTrustedAssembly"
)
{
return Type.GetType(parsed.AssemblyQualifiedName, throwOnError: true);
}
throw new InvalidOperationException($"Not allowed: '{untrustedInput.ToString()}'");
}
}
Новые API доступны из System.Reflection.Metadata
пакета NuGet, который можно использовать с версиями .NET нижнего уровня.
Регулярные выражения
[GeneratedRegex]
в свойствах
.NET 7 представил исходный генератор и соответствующий Regex
GeneratedRegexAttribute атрибут.
Следующий частичный метод будет создан с кодом, необходимым для реализации этого Regex
кода.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();
C# 13 поддерживает частичные свойства в дополнение к частичным методам, поэтому начиная с .NET 9 можно также использовать [GeneratedRegex(...)]
в свойстве.
Следующее частичное свойство является эквивалентом свойства предыдущего примера.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }
Regex.EnumerateSplits
Класс Regex предоставляет метод, аналогичный Split принципу String.Split метода. При этом String.Split
вы предоставляете один или несколько char
string
разделителей, а реализация разбивает входной текст на эти разделители. Вместо Regex.Split
указания разделителя в качестве char
или string
шаблона регулярных выражений указывается вместо указания разделителя.
В следующем примере показано Regex.Split
.
foreach (string s in Regex.Split("Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine($"Split: \"{s}\"");
}
// Output, split by all English vowels:
// Split: "H"
// Split: "ll"
// Split: ", w"
// Split: "rld! H"
// Split: "w "
// Split: "r"
// Split: " y"
// Split: ""
// Split: "?"
Однако только Regex.Split
принимает входные string
данные и не поддерживает входные данные, предоставляемые ReadOnlySpan<char>
в качестве. Кроме того, он выводит полный набор разбиений в виде string[]
, который требует выделения как массива для хранения результатов, так string
и string
для каждого разделения. В .NET 9 новый EnumerateSplits метод позволяет выполнять ту же операцию, но с входными данными на основе диапазона и без каких-либо выделений для результатов. Он принимает ReadOnlySpan<char>
и возвращает перечисление Range объектов, представляющих результаты.
В следующем примере показано Regex.EnumerateSplits
использование входных ReadOnlySpan<char>
данных.
ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"Split: \"{input[r]}\"");
}
Сериализация (System.Text.Json)
- Параметры отступа
- Одноэлемент веб-параметров по умолчанию
- JsonSchemaExporter
- Примечания, допускающие значение NULL
- Требовать не необязательные параметры конструктора
- Порядок свойств JsonObject
- Настройка имен элементов перечисления
- Потоковая передача нескольких документов JSON
Параметры отступа
JsonSerializerOptions включает новые свойства, позволяющие настроить символ отступа и размер отступа записанного JSON.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Одноэлемент веб-параметров по умолчанию
Если вы хотите сериализовать параметры по умолчанию, которые ASP.NET Core используются для веб-приложений, используйте новый JsonSerializerOptions.Web синглтон.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
JsonSchemaExporter
JSON часто используется для представления типов в сигнатурах методов в рамках схем удаленного вызова процедур. Он используется, например, как часть спецификаций OpenAPI или как часть вызова инструментов со службами ИИ, такими как OpenAI. Разработчики могут сериализовать и десериализировать типы .NET в формате JSON.System.Text.Json Но они также должны иметь возможность получить схему JSON, которая описывает форму типа .NET (т. е. описывает форму сериализации и десериализации). System.Text.Json теперь предоставляет JsonSchemaExporter тип, который поддерживает создание схемы JSON, представляющей тип .NET.
Дополнительные сведения см. в разделе "Экспорт схемы JSON".
Учитывайте аннотации, допускающие null
System.Text.Json теперь распознает заметки о допустимости NULL свойств и можно настроить для принудительного применения этих свойств во время сериализации и десериализации с помощью флага RespectNullableAnnotations .
В следующем коде показано, как задать параметр:
public static void RunIt()
{
JsonSerializerOptions options = new() { RespectNullableAnnotations = true };
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow getting null values.
// Consider updating its nullability annotation.
JsonSerializer.Serialize(new Book { Title = null! }, options);
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow setting null values.
// Consider updating its nullability annotation.
JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
}
public class Book
{
public required string Title { get; set; }
public string? Author { get; set; }
public int PublishYear { get; set; }
}
Дополнительные сведения см. в заметках, допускающих значение NULL.
Требовать не необязательные параметры конструктора
Исторически при System.Text.Json использовании десериализации на основе конструктора при использовании десериализации на основе конструктора параметры конструктора рассматриваются как необязательные параметры конструктора. Это поведение можно изменить с помощью нового RespectRequiredConstructorParameters флага.
В следующем коде показано, как задать параметр:
JsonSerializerOptions options = new() { RespectRequiredConstructorParameters = true };
// Throws exception: System.Text.Json.JsonException: JSON deserialization
// for type 'Serialization+MyPoco' was missing required properties including: 'Value'.
JsonSerializer.Deserialize<MyPoco>("""{}""", options);
Тип MyPoco
определяется следующим образом:
record MyPoco(string Value);
Дополнительные сведения см. в разделе параметров конструктора, не являющихся необязательными.
Порядок свойств JsonObject
Теперь тип JsonObject предоставляет упорядоченные словари, такие как API, которые обеспечивают явное управление порядком свойств.
JsonObject jObj = new()
{
["key1"] = true,
["key3"] = 3
};
Console.WriteLine(jObj is IList<KeyValuePair<string, JsonNode?>>); // True.
// Insert a new key-value pair at the correct position.
int key3Pos = jObj.IndexOf("key3") is int i and >= 0 ? i : 0;
jObj.Insert(key3Pos, "key2", "two");
foreach (KeyValuePair<string, JsonNode?> item in jObj)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
// Output:
// key1: true
// key2: two
// key3: 3
Дополнительные сведения см. в разделе "Управление порядком свойств".
Настройка имен элементов перечисления
Новый System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute атрибут можно использовать для настройки имен отдельных элементов перечисления для типов, сериализованных в виде строк:
JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"
[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
Дополнительные сведения см. в разделе "Пользовательские имена элементов перечисления".
Потоковая передача нескольких документов JSON
System.Text.Json.Utf8JsonReader теперь поддерживает чтение нескольких документов JSON, разделенных пробелами, из одного буфера или потока. По умолчанию средство чтения создает исключение, если он обнаруживает любые символы, отличные от пробелов, которые заканчиваются первым документом верхнего уровня. Это поведение можно изменить с помощью флага AllowMultipleValues .
Дополнительные сведения см. в разделе "Чтение нескольких документов JSON".
Диапазоны
В коде с высокой производительностью диапазоны часто используются, чтобы избежать ненужных выделений строк. Span<T> и ReadOnlySpan<T> продолжайте революцию в том, как код написан в .NET, и все выпуски добавляются все больше и больше методов, которые работают на диапазонах. .NET 9 включает следующие обновления, связанные с диапазоном:
- Вспомогательные средства файлов
-
params ReadOnlySpan<T>
Перегрузки - Перечисление по char< ReadOnlySpan>. Сегменты split()
Вспомогательные средства файлов
Теперь в File классе есть новые вспомогательные средства, которые легко и напрямую записываютReadOnlySpan<char>
/ReadOnlySpan<byte>
и ReadOnlyMemory<char>
/ReadOnlyMemory<byte>
в файлы.
Следующий код эффективно записывает в ReadOnlySpan<char>
файл.
ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);
Новые StartsWith<T>(ReadOnlySpan<T>, T) методы расширения EndsWith<T>(ReadOnlySpan<T>, T) также добавлены для диапазонов, что упрощает проверку того, ReadOnlySpan<T> начинается ли или заканчивается определенным T
значением.
В следующем коде используются эти новые удобные API.
ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false
params ReadOnlySpan<T>
Перегрузки
C# всегда поддерживает помеченные параметры массива как params
. Это ключевое слово включает упрощенный синтаксис вызова. Например, String.Join(String, String[]) второй параметр метода помечен как params
. Эту перегрузку можно вызвать с массивом или передать значения по отдельности:
string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");
До .NET 9 при отдельном передаче значений компилятор C# выдает код, идентичный первому вызову, создав неявный массив вокруг трех аргументов.
Начиная с C# 13, можно использовать params
любой аргумент, который можно создать с помощью выражения коллекции, включая диапазоны (Span<T> и ReadOnlySpan<T>). Это полезно для удобства использования и производительности. Компилятор C# может хранить аргументы в стеке, упаковывать диапазон вокруг них и передавать их в метод, что позволяет избежать неявного выделения массива, которое иначе привело бы к тому.
.NET 9 включает более 60 методов с параметром params ReadOnlySpan<T>
. Некоторые из них являются новыми перегрузками, и некоторые из них уже приняли методы ReadOnlySpan<T>
, но теперь имеют этот параметр, помеченный как params
. Чистый эффект заключается в обновлении до .NET 9 и повторной компиляции кода, вы увидите улучшения производительности без внесения изменений в код. Это связано с тем, что компилятор предпочитает привязаться к перегрузкам на основе диапазона, чем к перегрузкам на основе массива.
Например, String.Join
теперь включает в себя следующую перегрузку, которая реализует новый шаблон: String.Join(String, ReadOnlySpan<String>)
Теперь вызов string.Join(", ", "a", "b", "c")
выполняется без выделения массива для передачи в "a"
"b"
аргументы и "c"
аргументы.
Перечисление по char< ReadOnlySpan>. Сегменты split()
string.Split
— это удобный метод для быстрого секционирования строки с одним или несколькими предоставленными разделителями. Однако для кода, ориентированного на производительность, профиль string.Split
выделения может быть запретительным, так как он выделяет строку для каждого синтаксического анализа компонента и string[]
для хранения всех этих компонентов. Он также не работает с диапазонами, поэтому если у вас есть ReadOnlySpan<char>
, вы вынуждены выделить еще одну строку при преобразовании в строку, чтобы иметь возможность вызывать string.Split
ее.
В .NET 8 введены Split
набор SplitAny
и ReadOnlySpan<char>
методы. Вместо возврата нового string[]
метода эти методы принимают назначение Span<Range>
, в которое записываются ограничивающие индексы для каждого компонента. Это делает операцию полностью свободной от выделения. Эти методы подходят для использования, если число диапазонов известно и мало.
В .NET 9 новые перегрузки Split
и SplitAny
были добавлены, чтобы разрешить добавочный анализ ReadOnlySpan<T>
с предварительным неизвестным числом сегментов. Новые методы обеспечивают перечисление по каждому сегменту, который аналогично представлен как объект Range
, который можно использовать для среза в исходном диапазоне.
public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
foreach (Range segment in span.Split(','))
{
if (span[segment].SequenceEquals(item))
{
return true;
}
}
return false;
}
Система.Форматы
Позиция или смещение данных в заключивом потоке для TarEntry объекта теперь является общедоступным свойством. TarEntry.DataOffset возвращает позицию в архивном потоке записи, где находится первый байт данных записи. Данные записи инкапсулируются в подпотоке, к которому можно получить доступ TarEntry.DataStream, что скрывает реальную позицию данных относительно архивного потока. Это достаточно для большинства пользователей, но если вам нужна дополнительная гибкость и хотите знать реальную начальную позицию данных в архивном потоке, новый TarEntry.DataOffset API упрощает поддержку таких функций, как одновременный доступ с очень большими файлами TAR.
// Create stream for tar ball data in Azure Blob Storage.
BlobClient blobClient = new(connectionString, blobContainerName, blobName);
Stream blobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
// Create TarReader for the stream and get a TarEntry.
TarReader tarReader = new(blobClientStream);
System.Formats.Tar.TarEntry? tarEntry = await tarReader.GetNextEntryAsync();
if (tarEntry is null)
return;
// Get position of TarEntry data in blob stream.
long entryOffsetInBlobStream = tarEntry.DataOffset;
long entryLength = tarEntry.Length;
// Create a separate stream.
Stream newBlobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
newBlobClientStream.Seek(entryOffsetInBlobStream, SeekOrigin.Begin);
// Read tar ball content from separate BlobClient stream.
byte[] bytes = new byte[entryLength];
await newBlobClientStream.ReadExactlyAsync(bytes, 0, (int)entryLength);
System.Guid
NewGuid()
Guid
создает заполненную в основном криптографически защищенными случайными данными, следуя спецификации UUID версии 4 в RFC 9562. Этот же RFC также определяет другие версии, включая версию 7, которая "содержит поле упорядоченного по времени значения, производное от широко реализованного и хорошо известного источника метки времени эпохи Unix". Другими словами, большая часть данных по-прежнему является случайной, но некоторые из них зарезервированы для данных на основе метки времени, что позволяет этим значениям иметь естественный порядок сортировки. В .NET 9 можно создать Guid
в соответствии с версией 7 с помощью новых Guid.CreateVersion7() и Guid.CreateVersion7(DateTimeOffset) методов. Вы также можете использовать новое Version свойство для получения Guid
поля версии объекта.
System.IO
Сжатие с помощью zlib-ng
System.IO.Compressionтакие функции, как ZipArchive, DeflateStreamGZipStreamи ZLibStream все они основаны в первую очередь на библиотеке zlib. Начиная с .NET 9, эти функции вместо этого используют zlib-ng, библиотеку, которая обеспечивает более согласованную и эффективную обработку в более широком массиве операционных систем и оборудования.
Параметры сжатия ZLib и Brotli
ZLibCompressionOptionsи BrotliCompressionOptions являются новыми типами для настройки уровня сжатия и стратегии для конкретного алгоритма (Default
, , Filtered
, HuffmanOnly
RunLengthEncoding
илиFixed
). Эти типы предназначены для пользователей, которые хотят более точно настроенных параметров, чем единственный существующий параметр <System.IO.Compression.CompressionLevel>.
Новые типы параметров сжатия могут быть расширены в будущем.
В следующем фрагменте кода показан пример использования:
private MemoryStream CompressStream(Stream uncompressedStream)
{
MemoryStream compressorOutput = new();
using ZLibStream compressionStream = new(
compressorOutput,
new ZLibCompressionOptions()
{
CompressionLevel = 6,
CompressionStrategy = ZLibCompressionStrategy.HuffmanOnly
}
);
uncompressedStream.CopyTo(compressionStream);
compressionStream.Flush();
return compressorOutput;
}
Документы XPS из виртуального принтера XPS
Документы XPS, поступающие из виртуального принтера V4 XPS, ранее не были открыты с помощью System.IO.Packaging библиотеки, из-за отсутствия поддержки обработки файлов .piece . Этот разрыв был устранен в .NET 9.
System.Numerics
- Верхний предел BigInteger
-
BigMul
Пчела - API преобразования векторов
- API-интерфейсы создания вектора
- Дополнительное ускорение
Верхний предел BigInteger
BigInteger поддерживает представление целых значений по существу произвольной длины. Однако на практике длина ограничена ограничениями базового компьютера, например доступной памяти или времени, необходимого для вычисления заданного выражения. Кроме того, существуют некоторые API, которые завершаются сбоем при вводе, что приводит к слишком большому значению. Из-за этих ограничений .NET 9 применяет максимальную длину BigInteger
, которая заключается в том, что она может содержать не более (2^31) - 1
(приблизительно 2,14 миллиарда) битов. Такое число представляет почти 256 МБ выделения и содержит примерно 646,5 миллиона цифр. Это новое ограничение гарантирует, что все предоставляемые API хорошо работают и согласованы, позволяя числам, которые находятся далеко за пределами большинства сценариев использования.
BigMul
Пчела
BigMul
— это операция, которая создает полный продукт двух чисел. .NET 9 добавляет выделенные BigMul
API в int
, long
uint
и ulong
тип возврата которого является следующим большим целым типом, чем типы параметров.
Новые API:
-
BigMul(Int32, Int32) (возвращает
long
) -
BigMul(Int64, Int64) (возвращает
Int128
) -
BigMul(UInt32, UInt32) (возвращает
ulong
) -
BigMul(UInt64, UInt64) (возвращает
UInt128
)
API преобразования векторов
.NET 9 добавляет выделенные API расширения для преобразования между Vector2, , Vector3, Vector4и QuaternionPlane.
Новые API приведены следующим образом:
- AsPlane(Vector4)
- AsQuaternion(Vector4)
- AsVector2(Vector4)
- AsVector3(Vector4)
- AsVector4(Plane)
- AsVector4(Quaternion)
- AsVector4(Vector2)
- AsVector4(Vector3)
- AsVector4Unsafe(Vector2)
- AsVector4Unsafe(Vector3)
Для одноразмерных преобразований, таких как между Vector4
, Quaternion
и Plane
, эти преобразования равны нулю стоимости. То же самое можно сказать для сужения преобразований, таких как от Vector4
Vector2
или Vector3
. Для расширения преобразований, таких как от Vector2
или Vector3
Vector4
до, существует обычный API, который инициализирует новые элементы до 0, и Unsafe
суффиксированного API, который оставляет эти новые элементы неопределенными и поэтому может быть нулевой стоимостью.
API-интерфейсы создания вектора
Существуют новые Create
API, предоставляемые для Vector, Vector2Vector3и Vector4 это четность эквивалентных API, предоставляемых для типов векторов оборудования, предоставляемых в System.Runtime.Intrinsics пространстве имен.
Дополнительные сведения о новых API см. в следующих статье:
Эти API в первую очередь предназначены для удобства и общей согласованности в разных странах. Типы с ускорением SIMD в NET.
Дополнительное ускорение
Дополнительные улучшения производительности были улучшены для многих типов в System.Numerics пространстве имен, включая BigInteger, , Vector2, Vector3, Vector4Quaternionи Plane.
В некоторых случаях это привело к скорости 2-5x для основных API, включая Matrix4x4
умножение, создание Plane из ряда вершин, Quaternion объединение и вычисление кросс-продукта.Vector3
Существует также постоянная поддержка свертывания API SinCos
, которая вычисляет как Sin(x)
один вызов, так и Cos(x)
в одном вызове, что делает его более эффективным.
Tensors для искусственного интеллекта
Tensors — это краеугольный камень структуры данных искусственного интеллекта (ИИ). Их часто можно рассматривать как многомерные массивы.
Тензоры используются для:
- Представляет и кодирует такие данные, как текстовые последовательности (токены), изображения, видео и звук.
- Эффективнее управлять данными с более высокими размерами.
- Эффективное применение вычислений для более высоких размерных данных.
- Храните сведения о весе и промежуточные вычисления (в нейронных сетях).
Чтобы использовать API-интерфейсы тензоров .NET, установите пакет NuGet System.Numerics.Tensors .
Новый тип Tensor<T>
Новый Tensor<T> тип расширяет возможности ИИ библиотек и среды выполнения .NET. Этот тип:
- Обеспечивает эффективное взаимодействие с библиотеками ИИ, такими как ML.NET, TorchSharp и ONNX Runtime, используя ноль копий, где это возможно.
- Строится на основе TensorPrimitives для эффективных математических операций.
- Позволяет легко и эффективно манипулировать данными, предоставляя операции индексирования и срезов.
- Не является заменой существующих библиотек ИИ и машинного обучения. Вместо этого он предназначен для предоставления общего набора API для уменьшения дублирования кода и зависимостей, а также для повышения производительности с помощью последних функций среды выполнения.
В следующих кодах показаны некоторые API, включенные в новый Tensor<T>
тип.
// Create a tensor (1 x 3).
Tensor<int> t0 = Tensor.Create([1, 2, 3], [1, 3]); // [[1, 2, 3]]
// Reshape tensor (3 x 1).
Tensor<int> t1 = t0.Reshape(3, 1); // [[1], [2], [3]]
// Slice tensor (2 x 1).
Tensor<int> t2 = t1.Slice(1.., ..); // [[2], [3]]
// Broadcast tensor (3 x 1) -> (3 x 3).
// [
// [ 1, 1, 1],
// [ 2, 2, 2],
// [ 3, 3, 3]
// ]
var t3 = Tensor.Broadcast<int>(t1, [3, 3]);
// Math operations.
var t4 = Tensor.Add(t0, 1); // [[2, 3, 4]]
var t5 = Tensor.Add(t0.AsReadOnlyTensorSpan(), t0); // [[2, 4, 6]]
var t6 = Tensor.Subtract(t0, 1); // [[0, 1, 2]]
var t7 = Tensor.Subtract(t0.AsReadOnlyTensorSpan(), t0); // [[0, 0, 0]]
var t8 = Tensor.Multiply(t0, 2); // [[2, 4, 6]]
var t9 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t0); // [[1, 4, 9]]
var t10 = Tensor.Divide(t0, 2); // [[0.5, 1, 1.5]]
var t11 = Tensor.Divide(t0.AsReadOnlyTensorSpan(), t0); // [[1, 1, 1]]
Замечание
Этот API помечен как экспериментальный для .NET 9.
TensorPrimitives (Тензорные Примитивы)
Библиотека System.Numerics.Tensors
включает TensorPrimitives класс, который предоставляет статические методы для выполнения числовых операций по диапазонам значений. В .NET 9 область применения методов, предоставляемых TensorPrimitives в .NET, была значительно расширена, увеличиваясь с 40 (в .NET 8) до почти 200 перегрузков. Область поверхности охватывает знакомые числовые операции из типов, таких как Math и MathF. Он также включает универсальные математические интерфейсы, такие как INumber<TSelf>, за исключением обработки отдельного значения, они обрабатывают диапазон значений. Многие операции также были ускорены с помощью оптимизированных для SIMD реализаций для .NET 9.
TensorPrimitives теперь предоставляет универсальные перегрузки для любого типа T
, реализующего определенный интерфейс. (Версия .NET 8 включала только перегрузки для управления диапазонами значений float
.) Например, новая CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) перегрузка выполняет совместное сходство двух векторов float
, double
значений или Half
значений любого другого типа, реализующего IRootFunctions<TSelf>.
Сравните точность операции сходства косинуса по двум векторам типа float
и double
:
ReadOnlySpan<float> vector1 = [1, 2, 3];
ReadOnlySpan<float> vector2 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector1, vector2));
// Prints 0.9746318
ReadOnlySpan<double> vector3 = [1, 2, 3];
ReadOnlySpan<double> vector4 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector3, vector4));
// Prints 0.9746318461970762
Нарезание резьбы
Интерфейсы API потоков включают улучшения для итерации задач, для приоритетных каналов, которые могут упорядочить их элементы вместо первого выхода (FIFO) и Interlocked.CompareExchange
для других типов.
Task.WhenEach
Добавлены различные полезные новые API для работы с Task<TResult> объектами. Новый Task.WhenEach метод позволяет выполнять итерацию по мере выполнения задач с помощью инструкции await foreach
. Вам больше не нужно делать такие действия, как многократно вызывать Task.WaitAny набор задач, чтобы выбрать следующий, который завершается.
Следующий код выполняет несколько HttpClient
вызовов и работает с результатами по мере их завершения.
using HttpClient http = new();
Task<string> dotnet = http.GetStringAsync("http://dot.net");
Task<string> bing = http.GetStringAsync("http://www.bing.com");
Task<string> ms = http.GetStringAsync("http://microsoft.com");
await foreach (Task<string> t in Task.WhenEach(bing, dotnet, ms))
{
Console.WriteLine(t.Result);
}
Приоритетный несвязанный канал
Пространство System.Threading.Channels имен позволяет создавать каналы в первую очередь с помощью CreateBounded методов и CreateUnbounded методов. С каналами FIFO элементы считываются из канала в том порядке, в который они были записаны. В .NET 9 был добавлен новый CreateUnboundedPrioritized метод, который упорядочивает элементы, такие как следующий элемент, считываемый из канала, является наиболее важным, в соответствии с Comparer<T>.Default или пользовательским IComparer<T>.
В следующем примере используется новый метод для создания канала, который выводит цифры 1–5 в порядке, даже если они записываются в канал в другом порядке.
Channel<int> c = Channel.CreateUnboundedPrioritized<int>();
await c.Writer.WriteAsync(1);
await c.Writer.WriteAsync(5);
await c.Writer.WriteAsync(2);
await c.Writer.WriteAsync(4);
await c.Writer.WriteAsync(3);
c.Writer.Complete();
while (await c.Reader.WaitToReadAsync())
{
while (c.Reader.TryRead(out int item))
{
Console.Write($"{item} ");
}
}
// Output: 1 2 3 4 5
Interlocked.CompareExchange для дополнительных типов
В предыдущих версиях .NET Interlocked.Exchange и были перегрузки для работы с Interlocked.CompareExchange, int
uint
long
ulong
nint
nuint
float
и double
, а также универсальной перегрузкой для работы с любым ссылочным типом.object
T
В .NET 9 существуют новые перегрузки для атомарной работы с byte
, sbyte
short
и ushort
. Кроме того, было удалено универсальное ограничение на универсальные Interlocked.Exchange<T>
и Interlocked.CompareExchange<T>
перегрузки, поэтому эти методы больше не ограничены только для работы с ссылочными типами. Теперь они могут работать с любым примитивным типом, который включает все перечисленные выше типы, а bool
char
также любой enum
тип.