Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym artykule opisano nowe funkcje w bibliotekach .NET dla .NET 9.
Base64Url
Base64 to schemat kodowania, który tłumaczy dowolne bajty na tekst składający się z określonego zestawu 64 znaków. Jest to typowe podejście do przesyłania danych i od dawna obsługiwane za pośrednictwem różnych metod, takich jak Convert.ToBase64String lub Base64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Jednak niektóre ze znaków, które wykorzystuje, sprawiają, że jest mniej niż idealny do użycia w pewnych sytuacjach, w których normalnie chciałbyś go zastosować, na przykład w ciągach zapytania. W szczególności 64 znaki składające się z tabeli Base64 zawierają znaki "+" i "/", z których oba mają własne znaczenie w adresach URL. Doprowadziło to do utworzenia schematu Base64Url, który jest podobny do Base64, ale używa nieco innego zestawu znaków, który sprawia, że jest odpowiedni do użycia w kontekstach adresów URL. .NET 9 zawiera nową klasę Base64Url, która zapewnia wiele przydatnych i zoptymalizowanych metod kodowania i dekodowania za pomocą Base64Url do i z różnych typów danych.
W poniższym przykładzie pokazano użycie nowej klasy.
ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);
BinaryFormatter
.NET 9 usuwa BinaryFormatter ze środowiska uruchomieniowego .NET. API są nadal obecne, ale ich implementacje zawsze rzucają wyjątek, niezależnie od typu projektu. Aby uzyskać więcej informacji na temat usuwania i opcji, jeśli wystąpi problem, zobacz BinaryFormatter migration guide (Przewodnik migracji binaryFormatter).
Zbiory
Typy kolekcji w .NET uzyskują następujące aktualizacje dla .NET 9:
- Wyszukiwanie w kolekcjach przy użyciu zakresów
OrderedDictionary<TKey, TValue>- Metoda PriorityQueue.Remove() umożliwia aktualizowanie priorytetu elementu w kolejce.
ReadOnlySet<T>
Wyszukiwania w kolekcji z użyciem zakresów
W kodzie o wysokiej wydajności spany są często używane, aby uniknąć niepotrzebnego przydzielania ciągów, a tablice wyszukiwania z typami takimi jak Dictionary<TKey,TValue> i HashSet<T> są często używane jako cache. Nie było jednak bezpiecznego, wbudowanego mechanizmu do wyszukiwania w tych typach kolekcji z zakresami. Dzięki nowej funkcji allows ref struct w języku C# 13 i nowych funkcji dla tych typów kolekcji w .NET 9 można teraz wykonywać tego rodzaju wyszukiwania.
W poniższym przykładzie pokazano użycie
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>
W wielu scenariuszach można przechowywać pary klucz-wartość w sposób, w którym można zachować kolejność (listę par klucz-wartość), ale gdzie szybkie wyszukiwanie według klucza jest również obsługiwane (słownik par klucz-wartość). Od wczesnych dni .NET typ OrderedDictionary obsługuje ten scenariusz, ale tylko w sposób niegeneryczny, z kluczami i wartościami wpisanymi jako object. .NET 9 wprowadza długo oczekiwaną kolekcję OrderedDictionary<TKey,TValue>, która zapewnia wydajny, ogólny typ do obsługi tych scenariuszy.
Poniższy kod używa nowej klasy.
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]
metoda PriorityQueue.Remove()
.NET 6 wprowadzono kolekcję PriorityQueue<TElement,TPriority>, która zapewnia prostą i szybką implementację kopca tablicy. Jednym z problemów z kopcami tablicowymi jest to, że nie obsługują aktualizacji priorytetu, co sprawia, że są one trudne w użyciu w algorytmach, takich jak odmiany algorytmu Dijkstry.
Chociaż nie można zaimplementować wydajnych aktualizacji priorytetów $O(\log n)$ w istniejącej kolekcji, nowa PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) metoda umożliwia emulowanie aktualizacji priorytetów (choć w czasie $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);
}
Ta metoda odblokowuje użytkowników, którzy chcą implementować algorytmy grafu w kontekstach, w których wydajność asymptotyczna nie jest blokowaniem. (Takie konteksty obejmują edukację i tworzenie prototypów). Na przykład poniżej przedstawiono zabawkową implementację algorytmu Dijkstry, który używa nowego interfejsu API.
ReadOnlySet<T>
Często pożądane jest udostępnianie widoków kolekcji tylko do odczytu. ReadOnlyCollection<T> Umożliwia utworzenie otoki tylko do odczytu wokół dowolnego modyfikowalnego IList<T>elementu i ReadOnlyDictionary<TKey,TValue> umożliwia utworzenie otoki tylko do odczytu wokół dowolnego modyfikowalnego IDictionary<TKey,TValue>elementu . Jednak wcześniejsze wersje .NET nie miały wbudowanej obsługi tego samego w przypadku ISet<T>. .NET 9 wprowadza ReadOnlySet<T>, aby rozwiązać ten problem.
Nowa klasa umożliwia następujący wzorzec użycia.
private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;
public ReadOnlySet<int> Set => _setWrapper ??= new(_set);
Model składników — TypeDescriptor obsługa przycinania
System.ComponentModel Zawiera nowe interfejsy API zgodne z trymerem do opisywania składników. Każda aplikacja, szczególnie samodzielne przycinane aplikacje, może używać tych nowych interfejsów API, aby ułatwić obsługę scenariuszy przycinania.
Podstawowym interfejsem API jest metoda TypeDescriptor.RegisterType w klasie TypeDescriptor. Ta metoda ma atrybut DynamicallyAccessedMembersAttribute, aby trimmer zachowywał członków dla tego typu. Tę metodę należy wywołać raz na typ i zazwyczaj na początku.
Pomocnicze interfejsy API mają FromRegisteredType sufiks, taki jak TypeDescriptor.GetPropertiesFromRegisteredType(Type). W przeciwieństwie do ich odpowiedników, które nie mają sufiksu FromRegisteredType, ten interfejs API nie ma atrybutów trymera [RequiresUnreferencedCode] ani [DynamicallyAccessedMembers]. Brak atrybutów trymera pomaga konsumentom, ponieważ nie muszą już ani jednego z następujących:
- Tłumienie ostrzeżeń dotyczących przycinania, które mogą być ryzykowne.
- Propagacja silnie typizowanego
Typeparametru do innych metod może być kłopotliwa lub niewykonalna.
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; }
}
Aby uzyskać więcej informacji, zobacz propozycję interfejsu API .
Kryptografia
- Metoda CryptographicOperations.HashData()
- Algorytm KMAC
- algorytmyAES-GCM i ChaChaPoly1305 włączone dla systemów iOS/tvOS/MacCatalyst
- Ładowanie certyfikatu X.509
- Obsługa dostawców openSSL
- Zabezpieczenia Windows CNG oparte na wirtualizacji
Metoda CryptographicOperations.HashData()
.NET zawiera kilka statycznych implementacji funkcji skrótu typu "jednorazowego użytku" i powiązanych funkcji. Te interfejsy API obejmują SHA256.HashData i HMACSHA256.HashData. Interfejsy API jednorazowe są preferowane do użycia, ponieważ mogą zapewnić najlepszą możliwą wydajność i zmniejszyć lub wyeliminować alokacje.
Jeśli deweloper chce udostępnić interfejs API, który obsługuje wyznaczanie wartości skrótu, w którym obiekt wywołujący definiuje algorytm wyznaczania wartości skrótu, zwykle odbywa się to przez akceptowanie argumentu HashAlgorithmName . Jednak użycie tego wzorca z interfejsami API jednorazowego użytku wymagałoby przełączania się na każdą możliwą opcję HashAlgorithmName, a następnie użycie odpowiedniej metody. Aby rozwiązać ten problem, .NET 9 wprowadza interfejs API CryptographicOperations.HashData. Ten interfejs API umożliwia utworzenie skrótu lub HMAC dla danych wejściowych jako jednorazowej operacji, w której używany algorytm jest określany przez element HashAlgorithmName.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
Algorytm KMAC
.NET 9 udostępnia algorytm KMAC określony przez NIST SP-800-185. KECCAK Message Authentication Code (KMAC) to funkcja pseudolosowa i funkcja skrótu z kluczem oparta na KECCAK.
Następujące nowe klasy używają algorytmu KMAC. Użyj wystąpień, aby zebrać dane w celu utworzenia komputera MAC lub użyć metody statycznej HashData dla jednego rzutu danych wejściowych.
Funkcja KMAC jest dostępna w systemie Linux z programem OpenSSL 3.0 lub nowszym oraz w programie Windows 11 Build 26016 lub nowszym. Możesz użyć właściwości statycznej IsSupported , aby określić, czy platforma obsługuje żądany algorytm.
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.
}
algorytmy AES-GCM i ChaChaPoly1305 włączone dla systemów iOS/tvOS/MacCatalyst
IsSupported a ChaChaPoly1305.IsSupported teraz zwraca wartość true podczas uruchamiania w systemach iOS 13+, tvOS 13+i Mac Catalyst.
AesGcm Obsługuje tylko 16-bajtowe (128-bitowe) wartości tagów w systemach operacyjnych firmy Apple.
Ładowanie certyfikatu X.509
Od wersji .NET Framework 2.0 sposób ładowania certyfikatu został new X509Certificate2(bytes). Istnieją również inne wzorce, takie jak new X509Certificate2(bytes, password, flags), new X509Certificate2(path), new X509Certificate2(path, password, flags)i X509Certificate2Collection.Import(bytes, password, flags) (i jego przeciążenia).
Wszystkie te metody używały analizy zawartości, aby ustalić, czy dane wejściowe były czymś, co mogą obsłużyć, a następnie je załadować, jeśli mogły. Dla niektórych rozmówców ta strategia była bardzo wygodna. Ale ma również pewne problemy:
- Nie każdy format pliku działa w każdym systemie operacyjnym.
- Jest to odchylenie protokołu.
- Jest to źródło problemów z zabezpieczeniami.
.NET 9 wprowadza nową klasę X509CertificateLoader, która ma projekt "jedna metoda, jeden cel". W początkowej wersji obsługuje tylko dwa z pięciu formatów obsługiwanych przez konstruktora X509Certificate2 . Są to dwa formaty, które działały na wszystkich systemach operacyjnych.
Obsługa dostawców openSSL
.NET 8 zostały wprowadzone interfejsy API specyficzne dla OpenSSL OpenPrivateKeyFromEngine(String, String) i OpenPublicKeyFromEngine(String, String). Umożliwiają one interakcję ze składnikami OpenSSL ENGINE i używają na przykład sprzętowych modułów zabezpieczeń (HSM).
.NET 9 wprowadza SafeEvpPKeyHandle.OpenKeyFromProvider(String, String), co umożliwia korzystanie z dostawców OpenSSL i interakcję z dostawcami, takimi jak tpm2 lub pkcs11.
Niektóre dystrybucje usunęły ENGINE obsługę , ponieważ jest ona teraz przestarzała.
Poniższy fragment kodu przedstawia podstawowe użycie:
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.
}
Podczas uzgadniania protokołu TLS wprowadzono pewne ulepszenia wydajności, a także ulepszenia interakcji z kluczami prywatnymi RSA korzystającymi ze ENGINE składników.
Zabezpieczenia Windows oparte na wirtualizacji CNG (Cryptography Next Generation)
Windows 11 dodał nowe funkcje API, aby ułatwić zabezpieczanie kluczy Windows za pomocą zabezpieczeń opartych na wirtualizacji (VBS). Dzięki tej nowej funkcji klucze mogą być chronione przed atakami kradzieży kluczy na poziomie administratora z niewielkim wpływem na wydajność, niezawodność i skalę.
.NET 9 dodał pasujące flagi CngKeyCreationOptions. Dodano następujące trzy flagi:
-
CngKeyCreationOptions.PreferVbsDopasowanieNCRYPT_PREFER_VBS_FLAG -
CngKeyCreationOptions.RequireVbsdopasowanieNCRYPT_REQUIRE_VBS_FLAG -
CngKeyCreationOptions.UsePerBootKeydopasowanieNCRYPT_USE_PER_BOOT_KEY_FLAG
Poniższy fragment kodu pokazuje, jak używać jednej z 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.
}
Data i godzina — nowe przeciążenia TimeSpan.From*
Klasa TimeSpan oferuje kilka From* metod, które umożliwiają utworzenie TimeSpan obiektu przy użyciu klasy double. Jednak ponieważ double jest formatem zmiennoprzecinkowym opartym na liczbach binarnych, nieodłączna niedokładność może prowadzić do błędów. Na przykład TimeSpan.FromSeconds(101.832) może nie być dokładnie reprezentowane przez 101 seconds, 832 milliseconds, ale tylko w przybliżeniu przez 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds. Ta rozbieżność spowodowała częste zamieszanie i nie jest to również najbardziej wydajny sposób reprezentowania takich danych. Aby rozwiązać ten problem, .NET 9 dodaje nowe przeciążenia, które umożliwiają tworzenie obiektów TimeSpan z liczb całkowitych. Istnieją nowe przeciążenia z FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds, i FromMicroseconds.
Poniższy kod przedstawia przykład wywoływania double oraz jednego z nowych przeciążeń typu całkowitego.
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
Wstrzykiwanie zależności — ActivatorUtilities.CreateInstance konstruktor
Rozdzielczość konstruktora dla ActivatorUtilities.CreateInstance została zmieniona w .NET 9. Wcześniej konstruktor, który został jawnie oznaczony za pomocą atrybutu ActivatorUtilitiesConstructorAttribute , może nie być wywoływany, w zależności od kolejności konstruktorów i liczby parametrów konstruktora. Logika zmieniła się w .NET 9, tak aby konstruktor, który ma atrybut, jest zawsze wywoływany.
Diagnostyka
- Debug.Assert domyślnie raportuje warunek asercji
- Nowa metoda Activity.AddLink
- Metrics.Gauge instrument
- Out-of-proc Meter wildcard nasłuchiwanie
Warunek asercji jest zgłaszany przez Debug.Assert domyślnie
Debug.Assert jest często używany do sprawdzania poprawności warunków, które powinny być zawsze prawdziwe. Błąd zazwyczaj wskazuje usterkę w kodzie. Istnieje wiele przeciążeń Debug.Assert, z których najprostsze polega jedynie na zaakceptowaniu warunku:
Debug.Assert(a > 0 && b > 0);
Asercja kończy się niepowodzeniem, jeśli warunek jest fałszywy. Historycznie jednak takie asercje nie zawierały żadnych informacji o tym, jaki warunek zawiódł. Począwszy od .NET 9, jeśli żaden komunikat nie zostanie jawnie dostarczony przez użytkownika, asercja będzie zawierać tekstową reprezentację warunku. Na przykład dla poprzedniego przykładu asercji, zamiast otrzymywać komunikat w stylu:
Process terminated. Assertion failed.
at Program.SomeMethod(Int32 a, Int32 b)
Komunikat będzie teraz następujący:
Process terminated. Assertion failed.
a > 0 && b > 0
at Program.SomeMethod(Int32 a, Int32 b)
Nowa metoda Activity.AddLink
Wcześniej można było połączyć śledzenie Activity tylko z innymi kontekstami śledzenia podczas tworzenia elementu Activity. Nowy w .NET 9, interfejs API AddLink(ActivityLink) umożliwia łączenie obiektu Activity z innymi kontekstami śledzenia po jego utworzeniu. Ta zmiana jest również zgodna ze specyfikacjami OpenTelemetry .
ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);
Activity activity = new("LinkTest");
activity.AddLink(activityLink);
Instrument Metrics.Gauge
System.Diagnostics.Metrics teraz udostępnia instrument Gauge<T> zgodny ze specyfikacją OpenTelemetry. Instrument jest przeznaczony do rejestrowania wartości, które nie są addytywne, w przypadku wystąpienia zmian. Na przykład może mierzyć poziom szumu tła, w którym sumowanie wartości z wielu pomieszczeń byłoby niesensowne.
Gauge Instrument jest typem ogólnym, który może rejestrować dowolny typ wartości, taki jak int, doublelub decimal.
W poniższym przykładzie pokazano zastosowanie instrumentu 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" } });
Nasłuchiwanie poza procesem z użyciem znaku wieloznacznego Miernika
Istnieje już możliwość nasłuchiwania mierników spoza procesu przy użyciu dostawcy źródła zdarzeń System.Diagnostics.Metrics, ale przed .NET 9 trzeba było określić pełną nazwę miernika. W .NET 9 można nasłuchiwać wszystkich liczników przy użyciu symbolu wieloznakowego *, co umożliwia przechwytywanie metryk z każdego licznika w ramach procesu. Ponadto dodaje obsługę nasłuchiwania według prefiksu miernika, dzięki czemu można nasłuchiwać wszystkich mierników, których nazwy zaczynają się od określonego prefiksu. Na przykład określenie MyMeter* umożliwia nasłuchiwanie wszystkich mierników z nazwami rozpoczynającymi się od 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();
Klasa jest zdefiniowana MyEventListener w następujący sposób.
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]}");
}
}
}
Po wykonaniu kodu dane wyjściowe są następujące:
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
Możesz również użyć symbolu wieloznakowego, aby nasłuchiwać metryk za pomocą narzędzi do monitorowania, takich jak dotnet-counters.
LINQ
Wprowadzono nowe metody CountByAggregateBy . Te metody umożliwiają agregowanie stanu według klucza bez konieczności przydzielania grup pośrednich za pośrednictwem metody GroupBy.
CountBy pozwala szybko obliczyć częstotliwość każdego klucza. W poniższym przykładzie znajduje się słowo, które występuje najczęściej w ciągu tekstowym.
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 umożliwia implementowanie bardziej ogólnych przepływów pracy. W poniższym przykładzie pokazano, jak można obliczyć wyniki skojarzone z danym kluczem.
(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>) umożliwia szybkie wyodrębnianie domyślnego indeksu wyliczalnego. Teraz możesz napisać kod, taki jak poniższy fragment kodu, aby automatycznie indeksować elementy w kolekcji.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Generator źródła rejestrowania
Język C# 12 wprowadził konstruktory podstawowe, które umożliwiają definiowanie konstruktora bezpośrednio w deklaracji klasy. Generator źródła rejestrowania obsługuje teraz rejestrowanie przy użyciu klas, które mają podstawowy konstruktor.
public partial class ClassWithPrimaryConstructor(ILogger logger)
{
[LoggerMessage(0, LogLevel.Debug, "Test.")]
public partial void Test();
}
Różne
W tej sekcji znajdziesz informacje o:
allows ref struct używane w bibliotekach
Język C# 13 wprowadza możliwość ograniczenia parametru ogólnego za pomocą allows ref struct, co informuje kompilator i środowisko uruchomieniowe, że można użyć ref struct dla tego parametru ogólnego. Wiele interfejsów API, które są zgodne z tą funkcją, zostało teraz oznaczonych adnotacjami. Na przykład metoda String.Create ma przeciążenie, które pozwala utworzyć string przez bezpośredni zapis w jej pamięci, reprezentowany jako zakres. Ta metoda ma TState argument przekazywany przez wywołującego do delegata, który wykonuje rzeczywiste zapisywanie.
Ten TState parametr typu String.Create jest teraz oznaczony jako allows ref struct:
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
where TState : allows ref struct;
Ta adnotacja umożliwia przekazanie fragmentu (lub dowolnego innego ref struct) jako danych wejściowych tej metody.
W poniższym przykładzie przedstawiono nowe przeciążenie String.ToLowerInvariant(), które korzysta z tej zdolności.
public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));
SearchValues ekspansja
W .NET 8 wprowadzono typ SearchValues<T>, który zapewnia zoptymalizowane rozwiązanie do wyszukiwania określonych zestawów znaków lub bajtów w pamięci. W .NET 9 SearchValues zostało rozszerzone, aby wspierać wyszukiwanie podciągów w obrębie większego ciągu.
Poniższy przykład wyszukuje wiele nazw zwierząt w ramach wartości ciągu i zwraca indeks do pierwszego znalezionego.
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);
Ta nowa funkcja ma zoptymalizowaną implementację, która korzysta z obsługi SIMD na podstawowej platformie. Umożliwia również optymalizowanie typów wyższego poziomu. Na przykład teraz Regex wykorzystuje tę funkcjonalność jako część swojej implementacji.
Sieć
- SocketsHttpHandler jest domyślny w HttpClientFactory
- System.Net.ServerSentEvents
- Wznawianie protokołu TLS przy użyciu certyfikatów klienta w systemie Linux
- Polecenie ping i przekroczenie limitu czasu protokołu WebSocket
- Element HttpClientFactory domyślnie nie rejestruje już wartości nagłówków
SocketsHttpHandler jest domyślnym elementem w HttpClientFactory
HttpClientFactory domyślnie tworzy HttpClient obiekty wspierane przez HttpClientHandler element.
HttpClientHandler jest wspierany przez SocketsHttpHandler, który jest znacznie bardziej konfigurowalny, w tym także zarządzanie czasem trwania połączenia.
HttpClientFactory teraz używa SocketsHttpHandler domyślnie i konfiguruje go do ustawiania limitów okresów istnienia połączenia, aby dopasować je do okresu obrotu określonego w fabryce.
System.Net.ServerSentEvents
Zdarzenia wysyłane przez serwer (SSE) to prosty i popularny protokół przesyłania strumieniowego danych z serwera do klienta. Jest używany na przykład przez OpenAI w ramach przesyłania strumieniowego wygenerowanego tekstu z jego usług sztucznej inteligencji. Aby uprościć konsumpcję zdarzeń SSE, nowa System.Net.ServerSentEvents biblioteka udostępnia analizator do prostego przetwarzania zdarzeń wysyłanych przez serwer.
Poniższy kod demonstruje użycie nowej klasy.
Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
Console.WriteLine(e.Data);
}
Wznawianie protokołu TLS przy użyciu certyfikatów klienta w systemie Linux
Wznawianie protokołu TLS to funkcja protokołu TLS, która umożliwia wznowienie wcześniej ustanowionych sesji na serwerze. Pozwala to uniknąć kilku podróży w tę i z powrotem oraz oszczędza zasoby obliczeniowe podczas handshake TLS.
Wznawianie protokołu TLS zostało już obsługiwane w systemie Linux dla połączeń SslStream bez certyfikatów klienta. .NET 9 dodaje obsługę wznawiania wzajemnie uwierzytelnionych połączeń TLS, które są typowe w scenariuszach serwer-serwer. Funkcja jest włączana automatycznie.
Ping keep-alive i czas oczekiwania dla protokołu WebSocket
Nowe interfejsy API na ClientWebSocketOptions i WebSocketCreationOptions umożliwiają wybór wysyłania pingów i przerywanie połączenia, jeśli element równorzędny nie odpowiada na czas.
Do tej pory można określić parametr , KeepAliveInterval aby uniemożliwić zachowanie bezczynności połączenia, ale nie było wbudowanego mechanizmu wymuszania odpowiedzi elementu równorzędnego.
Poniższy przykład wysyła polecenie ping do serwera co 5 sekund i przerywa połączenie, jeśli nie odpowiada w ciągu sekundy.
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);
Element HttpClientFactory domyślnie nie rejestruje już wartości nagłówków
LogLevel.Trace zdarzenia rejestrowane przez HttpClientFactory nie zawierają już wartości nagłówka domyślnie. Możesz wyrazić zgodę na rejestrowanie wartości dla określonych nagłówków za pośrednictwem metody pomocniczej RedactLoggedHeaders .
Poniższy przykład redaguje wszystkie nagłówki, z wyjątkiem agenta użytkownika.
services.AddHttpClient("myClient")
.RedactLoggedHeaders(name => name != "User-Agent");
Aby uzyskać więcej informacji, zobacz wiadomość rejestracji HttpClientFactory domyślnie usuwa wartości nagłówków.
Refleksja
Utrwalone zestawy
W wersjach .NET Core i .NET 5-8 obsługa tworzenia zestawu i emitowania metadanych odbicia dla dynamicznie utworzonych typów była ograniczona do AssemblyBuilder. Brak obsługi zapisywania zestawu często stanowił przeszkodę dla klientów migrujących z .NET Framework do .NET Core. .NET 9 dodaje nowy typ, PersistedAssemblyBuilder, którego można użyć do zapisania emitowanego zestawu.
Aby utworzyć wystąpienie PersistedAssemblyBuilder, wywołaj jego konstruktor i przekaż nazwę zestawu, podstawowy zestaw - rdzeń, System.Private.CoreLib, aby odwołać się do podstawowych typów środowiska uruchomieniowego oraz opcjonalne atrybuty niestandardowe. Po emisji wszystkich elementów członkowskich do zestawu wywołaj metodę PersistedAssemblyBuilder.Save(String), aby utworzyć zestaw z ustawieniami domyślnymi. Jeśli chcesz ustawić punkt wejścia lub inne opcje, możesz wywołać PersistedAssemblyBuilder.GenerateMetadata i użyć metadanych zwracanych w celu zapisania zestawu. Poniższy kod przedstawia przykład tworzenia utrwalonego zestawu i ustawiania punktu wejścia.
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]));
}
Nowa PersistedAssemblyBuilder klasa zawiera obsługę plików PDB. Możesz emitować informacje o symbolach i używać ich do debugowania wygenerowanego zestawu. Interfejs API ma podobny kształt do implementacji .NET Framework. Aby uzyskać więcej informacji, zobacz Emitowanie symboli i generowanie PDB.
Analizowanie nazw typów
TypeName jest analizatorem dla nazw typów ECMA-335, które zapewniają wiele takich samych funkcji, jak System.Type tylko są oddzielone od środowiska uruchomieniowego. Składniki, takie jak serializatory i kompilatory, muszą analizować i przetwarzać nazwy typów. Na przykład kompilator natywnej AOT przełączył się na użycie polecenia TypeName.
Nowa TypeName klasa zapewnia:
Statyczne
ParseiTryParsemetody analizowania danych wejściowych reprezentowanych jakoReadOnlySpan<char>. Obie metody akceptują instancję klasyTypeNameParseOptions(zbiór opcji), która umożliwia dostosowanie parsowania.Name,FullNameiAssemblyQualifiedNamewłaściwości, które działają dokładnie tak jak ich odpowiedniki w System.Type.Wiele właściwości i metod, które zawierają dodatkowe informacje o samej nazwie:
-
IsArray,IsSZArray(SZoznacza jednowymiarową, zero-indeksowaną tablicę),IsVariableBoundArrayTypeiGetArrayRankdo pracy z tablicami. -
IsConstructedGenericType,GetGenericTypeDefinitioniGetGenericArgumentsdo pracy z nazwami typów ogólnych. -
IsByReforazIsPointerdo pracy ze wskaźnikami i odwołaniami zarządzanymi. -
GetElementType()do pracy ze wskaźnikami, odwołaniami i tablicami. -
IsNestediDeclaringTypedo pracy z typami zagnieżdżonymi. -
AssemblyName, który uwidacznia informacje o nazwie zestawu za pośrednictwem nowej AssemblyNameInfo klasy. W przeciwieństwie doAssemblyName, nowy typ jest niezmienny, a parsowanie nazw kultur nie powoduje utworzenia wystąpieńCultureInfo.
-
Oba TypeName typy i AssemblyNameInfo są niezmienne i nie zapewniają możliwości sprawdzania równości (nie implementują IEquatable). Porównywanie nazw zestawów jest proste, ale różne scenariusze muszą porównywać tylko podzbiór ujawnionych informacji (Name, Version, CultureName i PublicKeyOrToken).
Poniższy fragment kodu przedstawia przykładowe użycie.
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()}'");
}
}
Nowe interfejsy API są dostępne w pakiecie NuGet System.Reflection.Metadata, który może być używany z wersjami .NET na poziomie podrzędnym.
Wyrażenia regularne
[GeneratedRegex] na właściwościach
W .NET 7 wprowadzono generator źródłowy Regex oraz odpowiadający mu atrybut GeneratedRegexAttribute.
Następująca metoda częściowa będzie generowana na podstawie źródła z całym kodem niezbędnym do zaimplementowania tego elementu Regex.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();
Język C# 13 obsługuje częściowe właściwości oprócz metod częściowych, więc zaczynając od .NET 9, można również użyć [GeneratedRegex(...)] we właściwości.
Poniższa właściwość częściowa jest odpowiednikiem właściwości poprzedniego przykładu.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }
Regex.EnumerateSplits
Klasa Regex zapewnia metodę Split, podobną w koncepcji do metody String.Split. W przypadku String.Split należy podać co najmniej jeden separator typu char lub string, a implementacja dzieli tekst wejściowy według tych separatorów. W Regex.Split zamiast określać separator jako char lub string, jest on określony jako wzorzec wyrażenia regularnego.
W poniższym przykładzie przedstawiono 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 Jednak akceptuje string tylko jako dane wejściowe i nie obsługuje danych wejściowych dostarczanych jako ReadOnlySpan<char>. Ponadto zwraca pełny zestaw podziałów jako string[], który wymaga przydzielenia zarówno string tablicy do przechowywania wyników, jak i string dla każdego podziału. W .NET 9 nowa metoda EnumerateSplits umożliwia wykonywanie tej samej operacji, ale z danymi wejściowymi opartymi na zakresie i bez ponoszenia żadnej alokacji dla wyników. Akceptuje element ReadOnlySpan<char> i zwraca wyliczenie Range obiektów reprezentujących wyniki.
Poniższy przykład demonstruje Regex.EnumerateSplits, przyjmując ReadOnlySpan<char> jako dane wejściowe.
ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"Split: \"{input[r]}\"");
}
Serializacja (System.Text.Json)
- Opcje wcięcia
- Domyślne opcje sieciowe singleton
- JsonSchemaExporter
- Uwzględniaj adnotacje nullable
- Wymagaj parametrów konstruktora innego niż opcjonalny
- Określanie kolejności właściwości obiektu JsonObject
- Dostosowywanie nazw członów wyliczenia
- Przesyłanie strumieniowe wielu dokumentów JSON
Opcje wcięcia
JsonSerializerOptions zawiera nowe właściwości, które umożliwiają dostosowanie znaku wcięcia i rozmiaru wcięcia zapisanego kodu JSON.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Domyślne opcje webowe singleton
Jeśli chcesz serializować przy użyciu domyślnych opcji, których ASP.NET Core używa dla aplikacji internetowych, użyj nowego singletonu JsonSerializerOptions.Web.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
JsonSchemaExporter
Kod JSON jest często używany do reprezentowania typów w podpisach metod w ramach zdalnych schematów wywoływania procedur. Jest on używany na przykład w ramach specyfikacji interfejsu OpenAPI lub w ramach wywoływania narzędzi z usługami sztucznej inteligencji, takimi jak te z interfejsu OpenAI. Deweloperzy mogą serializować i deserializować typy .NET w formacie JSON przy użyciu System.Text.Json. Muszą jednak również mieć możliwość uzyskania schematu JSON opisującego kształt typu .NET (czyli opisuje kształt tego, co byłoby serializowane i co można deserializować). System.Text.Json teraz udostępnia typ JsonSchemaExporter, który obsługuje generowanie schematu JSON reprezentującego typ .NET.
Aby uzyskać więcej informacji, zobacz Eksporter schematu JSON.
Uwzględniać adnotacje nullowalne
System.Text.Json Teraz rozpoznaje adnotacje nullowalności związane z właściwościami i można je skonfigurować tak, aby były wymuszane podczas serializacji i deserializacji przy użyciu flagi RespectNullableAnnotations.
Poniższy kod pokazuje, jak ustawić opcję:
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; }
}
Aby uzyskać więcej informacji, zobacz Respektowanie adnotacji nullable.
Wymagaj nieopcjonalnych parametrów konstruktora
W przeszłości System.Text.Json traktował nieopcjonalne parametry konstruktora jako opcjonalne podczas korzystania z deserializacji bazującej na konstruktorze. To zachowanie można zmienić przy użyciu nowej RespectRequiredConstructorParameters flagi.
Poniższy kod pokazuje, jak ustawić opcję:
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);
Typ jest definiowany MyPoco w następujący sposób:
record MyPoco(string Value);
Aby uzyskać więcej informacji, zobacz Parametry konstruktora nie opcjonalnego.
Określanie kolejności właściwości obiektu JsonObject
Typ JsonObject udostępnia teraz uporządkowane interfejsy API, takie jak słowniki, które umożliwiają jawne manipulowanie kolejnością właściwości.
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
Aby uzyskać więcej informacji, zobacz Manipulowanie kolejnością właściwości.
Dostosowywanie nazw członków wyliczenia
Nowy System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute atrybut może służyć do dostosowywania nazw poszczególnych składowych wyliczenia dla typów, które są serializowane jako ciągi:
JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"
[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
Aby uzyskać więcej informacji, zobacz Niestandardowe nazwy składowych wyliczenia.
Przesyłanie strumieniowe wielu dokumentów JSON
System.Text.Json.Utf8JsonReader Obsługuje teraz odczytywanie wielu dokumentów JSON rozdzielonych odstępami z jednego buforu lub strumienia. Domyślnie czytelnik zgłasza wyjątek, jeśli wykryje znaki inne niż spacje, które występują po pierwszym dokumencie najwyższego poziomu. To zachowanie można zmienić przy użyciu flagi AllowMultipleValues .
Aby uzyskać więcej informacji, zobacz Odczytywanie wielu dokumentów JSON.
Zakres
W kodzie o wysokiej wydajności zakresy są często używane, aby uniknąć niepotrzebnego przydzielania ciągów. Span<T> i ReadOnlySpan<T> nadal rewolucjonizują sposób pisania kodu w .NET, a z każdą wersją dodawanych jest coraz więcej metod, które działają na zakresach. .NET 9 zawiera następujące aktualizacje związane z zakresem:
- Pomocnicy plików
-
params ReadOnlySpan<T>Przeciążenia - Wyliczanie za pośrednictwem znaku< ReadOnlySpan>. Segmenty Split()
Pomocnicy plików
Klasa File ma teraz nowe pomocniki do łatwego i bezpośredniego zapisuReadOnlySpan<char>/ReadOnlySpan<byte>i ReadOnlyMemory<char>/ReadOnlyMemory<byte> do plików.
Poniższy kod efektywnie zapisuje element ReadOnlySpan<char> w pliku.
ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);
Dodano również nowe metody rozszerzenia StartsWith<T>(ReadOnlySpan<T>, T) i EndsWith<T>(ReadOnlySpan<T>, T) dla zakresów, co ułatwia testowanie, czy ReadOnlySpan<T> rozpoczyna lub kończy się określoną wartością T.
Poniższy kod używa tych nowych interfejsów API ułatwień.
ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false
params ReadOnlySpan<T> Przeciążenia
Język C# zawsze obsługiwał oznaczanie parametrów tablicy jako params. To słowo kluczowe umożliwia uproszczony sposób wywoływania. Na przykład String.Join(String, String[]) drugi parametr metody jest oznaczony znakiem params. To przeciążenie można wywołać za pomocą tablicy lub przekazując wartości indywidualnie:
string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");
Przed .NET 9, po przekazaniu wartości indywidualnie kompilator języka C# emituje kod identyczny z pierwszym wywołaniem, tworząc niejawną tablicę wokół trzech argumentów.
Począwszy od języka C# 13, można używać params z dowolnym argumentem, który można skonstruować za pomocą wyrażenia kolekcji, w tym z przedziałami (Span<T> i ReadOnlySpan<T>). Jest to korzystne dla użyteczności i wydajności. Kompilator języka C# może przechowywać argumenty na stosie, opakowować wokół nich zakres i przekazywać je do metody, co pozwala uniknąć niejawnej alokacji tablicy, która w przeciwnym razie doprowadziłaby do tego.
.NET 9 zawiera ponad 60 metod z parametrem params ReadOnlySpan<T>. Niektóre są zupełnie nowymi przeciążeniami, a niektóre to istniejące metody, które już przyjmowały ReadOnlySpan<T>, ale teraz ten parametr jest oznaczony jako params. W efekcie, jeśli uaktualnisz do .NET 9 i ponownie skompilujesz kod, zauważysz poprawę wydajności bez konieczności wprowadzania jakichkolwiek zmian w kodzie. Dzieje się tak dlatego, że kompilator preferuje powiązanie z przeciążeniami opartymi na zakresie niż z przeciążeniami opartymi na tablicy.
Na przykład String.Join teraz zawiera następujące przeciążenie, które wdraża nowy wzorzec: String.Join(String, ReadOnlySpan<String>)
Teraz wywołanie takie jak string.Join(", ", "a", "b", "c") jest wykonywane bez potrzeby przydzielania tablicy do przekazania argumentów "a", "b" i "c".
Wyliczaj po segmentach uzyskanych z ReadOnlySpan.Split()
string.Split jest wygodną metodą szybkiego dzielenia ciągu jednym lub więcej dostarczonymi separatorami. Jednak w przypadku kodu skoncentrowanego na wydajności, profil alokacji string.Split może być zbyt wysoki, ponieważ alokuje ciąg znaków dla każdego przeanalizowanego składnika oraz string[], aby przechowywać je wszystkie. Nie działa również z rozpiętościami, więc jeśli masz ReadOnlySpan<char> element, musisz przydzielić kolejny ciąg, gdy konwertujesz go na ciąg, aby móc wywołać string.Split.
W .NET 8 wprowadzono zestaw metod Split i SplitAny dla ReadOnlySpan<char>. Zamiast zwracać nowy string[], te metody akceptują miejsce docelowe Span<Range>, do którego są zapisywane indeksy graniczne każdego składnika. Dzięki temu operacja jest w pełni wolna od alokacji. Te metody są odpowiednie do użycia, gdy liczba zakresów jest znana i mała.
W .NET 9 dodano nowe przeciążenia Split i SplitAny, aby umożliwić przyrostowe analizowanie ReadOnlySpan<T> z a priori nieznaną liczbą segmentów. Nowe metody umożliwiają wyliczanie za pomocą każdego segmentu, który jest podobnie reprezentowany jako element Range , który może służyć do fragmentowania w oryginalnym zakresie.
public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
foreach (Range segment in span.Split(','))
{
if (span[segment].SequenceEquals(item))
{
return true;
}
}
return false;
}
System.Formats
Pozycja lub przesunięcie danych w strumieniu otaczającym obiekt TarEntry to teraz właściwość publiczna. TarEntry.DataOffset Zwraca pozycję w strumieniu archiwum wpisu, w którym znajduje się pierwszy bajt danych wpisu. Dane wpisu są hermetyzowane w podstreamie, do którego można uzyskać dostęp za pośrednictwem elementu TarEntry.DataStream, co ukrywa rzeczywistą pozycję danych względem strumienia archiwalnego. Jest to wystarczające dla większości użytkowników, ale jeśli potrzebujesz większej elastyczności i chcesz poznać rzeczywistą pozycję początkową danych w strumieniu archiwum, nowy TarEntry.DataOffset interfejs API ułatwia obsługę funkcji, takich jak współbieżny dostęp z bardzo dużymi plikami 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() Tworzy Guid wypełniony głównie kryptograficznie bezpiecznymi danymi losowymi, zgodnie ze specyfikacją UUID w wersji 4 w standardzie RFC 9562. To samo RFC definiuje również inne wersje, w tym wersję 7, która "zawiera pole wartości uporządkowane czasowo pochodzące z powszechnie zaimplementowanego i dobrze znanego źródła sygnatury czasowej systemu Unix Epoka". Innymi słowy, większość danych jest nadal losowa, ale niektóre z nich są zarezerwowane dla danych na podstawie sygnatury czasowej, która umożliwia tym wartościom naturalną kolejność sortowania. W .NET 9 można utworzyć Guid zgodnie z wersją 7 za pośrednictwem nowych metod Guid.CreateVersion7() i Guid.CreateVersion7(DateTimeOffset). Możesz również użyć nowej Version właściwości, aby pobrać Guid pole wersji obiektu.
System.IO
Kompresja zlib-ng
System.IO.Compression funkcje takie jak ZipArchive, DeflateStream, GZipStreami ZLibStream są oparte głównie na bibliotece zlib. Począwszy od .NET 9, wszystkie te funkcje używają zlib-ng biblioteki, która zapewnia bardziej spójne i wydajne przetwarzanie w szerszej gamie systemów operacyjnych i sprzętu.
Opcje kompresji ZLib i Brotli
ZLibCompressionOptions i BrotliCompressionOptions są nowymi typami do ustawienia poziomu i strategii specyficznych dla algorytmu (Default, Filtered, HuffmanOnly, RunLengthEncoding, lub Fixed). Te typy są przeznaczone dla użytkowników, którzy chcą bardziej dostroić ustawienia niż tylko istniejąca opcja <System.IO.Compression.CompressionLevel>.
Nowe typy opcji kompresji mogą zostać rozwinięte w przyszłości.
Poniższy fragment kodu przedstawia przykładowe użycie:
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;
}
Dokumenty XPS z drukarki wirtualnej XPS
Nie można było wcześniej otworzyć dokumentów XPS pochodzących z wirtualnej drukarki XPS w wersji 4 przy użyciu biblioteki System.IO.Packaging z powodu braku obsługi plików piece. Ta luka została rozwiązana w .NET 9.
System.Numerics
- Górna granica BigInteger
-
BigMulAPI - API konwersji wektorów
- API tworzenia wektorów
- Dodatkowe przyspieszenie
Górna granica BigInteger
BigInteger program obsługuje reprezentowanie wartości całkowitych o zasadniczo dowolnej długości. Jednak w praktyce długość jest ograniczona przez limity komputera bazowego, takie jak dostępna pamięć lub czas obliczenia danego wyrażenia. Ponadto istnieją pewne interfejsy API, które zawodzą przy danych wejściowych, które powodują zbyt dużą wartość. Ze względu na te limity .NET 9 wymusza maksymalną długość BigInteger, co oznacza, że może zawierać nie więcej niż (2^31) - 1 (około 2,14 miliarda) bitów. Taka liczba reprezentuje prawie 256 MB alokacji i zawiera około 646,5 miliona cyfr. Ten nowy limit gwarantuje, że wszystkie udostępnione interfejsy API są poprawnie funkcjonujące i spójne, jednocześnie umożliwiając stosowanie wartości liczbowych, które wykraczają daleko poza większość scenariuszy użycia.
BigMul API
BigMul jest operacją tworzącą pełny iloczyn dwóch liczb. .NET 9 dodaje dedykowane API BigMul na int, long, uint i ulong, których typem zwracanym jest następny większy typ całkowity niż typy parametrów.
Nowe interfejsy API to następujące:
-
BigMul(Int32, Int32) (zwraca
long) -
BigMul(Int64, Int64) (zwraca
Int128) -
BigMul(UInt32, UInt32) (zwraca
ulong) -
BigMul(UInt64, UInt64) (zwraca
UInt128)
Interfejsy API konwersji wektorów
.NET 9 dodaje dedykowane interfejsy API rozszerzeń do konwertowania między Vector2, Vector3, Vector4, Quaternion i Plane.
Nowe interfejsy API są następujące:
- AsPlane(Vector4)
- AsQuaternion(Vector4)
- AsVector2(Vector4)
- AsVector3(Vector4)
- AsVector4(Plane)
- AsVector4(Quaternion)
- AsVector4(Vector2)
- AsVector4(Vector3)
- AsVector4Unsafe(Vector2)
- AsVector4Unsafe(Vector3)
W przypadku konwersji o takich samych rozmiarach, jak między Vector4, Quaternion, i Plane, te konwersje są bezkosztowe. To samo można powiedzieć w przypadku konwersji zawężających, takich jak od Vector4 do Vector2 lub Vector3. W przypadku konwersji rozszerzających, takich jak z Vector2 lub Vector3 do Vector4, istnieje standardowe API, które inicjalizuje nowe elementy na 0, oraz interfejs API z sufiksem Unsafe, który pozostawia te nowe elementy niezdefiniowane, co może być bezkosztowe.
API do tworzenia wektorów
Istnieją nowe interfejsy API Create uwidocznione dla Vector, Vector2, Vector3 i Vector4, które odpowiadają równoważnym interfejsom API dostępnym dla typów wektorów sprzętowych dostępnych w przestrzeni nazw System.Runtime.Intrinsics.
Aby uzyskać więcej informacji na temat nowych interfejsów API, zobacz:
Te API są przeznaczone głównie dla zapewnienia wygody i ogólnej spójności wśród przyspieszonych typów SIMD w .NET.
Dodatkowe przyspieszenie
Wprowadzono dodatkowe ulepszenia wydajności dla wielu typów w przestrzeni nazw System.Numerics, w tym dla BigInteger, Vector2, Vector3, Vector4, Quaternion i Plane.
W niektórych przypadkach spowodowało to 2 do 5-krotne przyspieszenie kluczowych interfejsów API, w tym Matrix4x4 mnożenie, tworzenie Plane z serii wierzchołków, Quaternion łączenie i obliczanie iloczynu wektorowego Vector3.
Istnieje również obsługa stałego składania dla interfejsu SinCos API, który oblicza zarówno Sin(x), jak i Cos(x) w jednym wywołaniu, co zwiększa jego wydajność.
Tensory w sztucznej inteligencji
Tensors to podstawowa struktura danych sztucznej inteligencji (AI). Często można je traktować jako tablice wielowymiarowe.
Tensory są używane do:
- Reprezentacja i kodowanie danych, takich jak sekwencje tekstowe (tokeny), obrazy, wideo i dźwięk.
- Efektywne manipulowanie danymi o wyższych wymiarach.
- Efektywne stosowanie obliczeń na danych o wyższych wymiarach.
- Przechowywanie informacji o wadze i obliczeń pośrednich (w sieciach neuronowych).
Aby używać interfejsów API do obsługi tensorów .NET, zainstaluj pakiet NuGet System.Numerics.Tensors.
Nowy typ tensor<T>
Nowy typ Tensor<T> rozszerza możliwości sztucznej inteligencji bibliotek .NET i środowiska uruchomieniowego. Ten typ:
- Zapewnia wydajną interakcję z bibliotekami sztucznej inteligencji, takimi jak ML.NET, TorchSharp i ONNX Runtime, przy użyciu zerowych kopii tam, gdzie to możliwe.
- Opiera się na TensorPrimitives dla wydajnych operacji matematycznych.
- Umożliwia łatwe i wydajne manipulowanie danymi, zapewniając operacje indeksowania i fragmentowania.
- Nie zastępuje istniejących bibliotek sztucznej inteligencji i uczenia maszynowego. Zamiast tego ma na celu zapewnienie wspólnego zestawu interfejsów API w celu zmniejszenia duplikowania i zależności kodu oraz uzyskania lepszej wydajności przy użyciu najnowszych funkcji środowiska uruchomieniowego.
Poniższe kody zawierają niektóre interfejsy API dołączone do nowego Tensor<T> typu.
// 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]]
Uwaga / Notatka
Ten interfejs API jest oznaczony jako experimental dla .NET 9.
Prymitywy Tensorów
Biblioteka System.Numerics.Tensors zawiera klasę TensorPrimitives , która udostępnia metody statyczne do wykonywania operacji liczbowych na zakresach wartości. W .NET 9 zakres metod uwidocznionych przez TensorPrimitives został znacznie rozszerzony, rośnie z 40 (w .NET 8) do prawie 200 przeciążeń. Obszar powierzchni obejmuje znane operacje liczbowe z typów takich jak Math i MathF. Zawiera również ogólne interfejsy matematyczne, takie jak INumber<TSelf>, z wyjątkiem tego, że zamiast przetwarzać pojedynczą wartość, przetwarzają zakres wartości. Wiele operacji zostało również przyspieszonych dzięki implementacjom zoptymalizowanym pod kątem SIMD dla .NET 9.
TensorPrimitives Teraz uwidacznia ogólne przeciążenia dla dowolnego typu T , który implementuje określony interfejs. (Wersja .NET 8 zawiera tylko przeciążenia do manipulowania zakresami wartości float). Na przykład nowe przeciążenie CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) wykonuje obliczenia podobieństwa cosinusowego dla dwóch wektorów float, double lub Half, lub wartości każdego innego typu, który implementuje IRootFunctions<TSelf>.
Porównaj precyzję operacji podobieństwa cosinus na dwóch wektorach typu float a 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
Wątkowanie
Interfejsy API wątków obejmują ulepszenia iteracji w obrębie zadań, dla kanałów o wyższym priorytecie, które mogą porządkować elementy zamiast obsługiwać je w trybie pozornie pierwsze weszło, pierwsze wyszło (FIFO), oraz Interlocked.CompareExchange obsługujące więcej typów.
Task.WhenEach
Dodano różne przydatne nowe interfejsy API do pracy z obiektami Task<TResult> . Nowa Task.WhenEach metoda umożliwia iterowanie zadań w miarę ich ukończenia przy użyciu instrukcji await foreach. Nie musisz już wykonywać takich czynności, jak wielokrotne wywoływanie Task.WaitAny na zbiorze zadań, aby wybrać następne zadanie, które zostanie ukończone.
Poniższy kod wykonuje wiele wywołań HttpClient i działa na ich wynikach w miarę ich zakończenia.
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);
}
Priorytetowo niezwiązany kanał
System.Threading.Channels Przestrzeń nazw umożliwia tworzenie kanałów typu first-in-first-out (FIFO) za pomocą metod CreateBounded i CreateUnbounded. W przypadku kanałów FIFO elementy są odczytywane z kanału w kolejności, w której zostały do niego zapisane. W .NET 9 dodano nową metodę CreateUnboundedPrioritized, która porządkuje elementy tak, aby następny element odczytany z kanału był uważany za najważniejszy, zgodnie z Comparer<T>.Default lub niestandardowym IComparer<T>.
W poniższym przykładzie użyto nowej metody do utworzenia kanału, który generuje liczby od 1 do 5 w kolejności, mimo że są zapisywane w kanale w innej kolejności.
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 dla większej liczby typów
W poprzednich wersjach .NET Interlocked.Exchange i Interlocked.CompareExchange miały przeciążenia do pracy z int, uint, long, ulong, nint, nuint, float, double i object, a także ogólne przeciążenie do pracy z dowolnym typem odwołania T. W .NET 9 istnieją nowe przeciążenia do niepodzielnej pracy z byte, sbyte, short i ushort. Ponadto ograniczenie ogólne dla ogólnego Interlocked.Exchange<T> i Interlocked.CompareExchange<T> przeciążenia zostały usunięte, więc te metody nie są już ograniczone do pracy tylko z typami referencyjnymi. Mogą teraz pracować z dowolnym typem pierwotnym, który obejmuje wszystkie wyżej wymienione typy, a także bool i char, a także dowolny enum typ.