Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel werden neue Features in den .NET-Bibliotheken für .NET 9 beschrieben.
Base64Url
Base64 ist ein Codierungsschema, das beliebige Bytes in Text übersetzt, der aus einem bestimmten Satz von 64 Zeichen besteht. Es ist ein gängiger Ansatz zum Übertragen von Daten und wird seit langem über eine Vielzahl von Methoden unterstützt, z. B. mit Convert.ToBase64String oder Base64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Einige der verwendeten Zeichen machen sie jedoch in manchen Situationen weniger geeignet, in denen Sie sie ansonsten verwenden möchten, z. B. in Abfragezeichenfolgen. Insbesondere enthalten die 64 Zeichen, die die Base64-Tabelle umfassen, "+" und "/", die beide ihre eigene Bedeutung in URLs haben. Dies führte zur Erstellung des Base64Url-Schemas, das Base64 ähnelt, aber einen etwas anderen Satz von Zeichen verwendet, der sie für die Verwendung in URLs-Kontexten geeignet macht. .NET 9 enthält die neue Base64Url Klasse, die viele hilfreiche und optimierte Methoden zum Codieren und Decodieren mit Base64Url und von einer Vielzahl von Datentypen bereitstellt.
Im folgenden Beispiel wird die Verwendung der neuen Klasse veranschaulicht.
ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);
BinaryFormatter
.NET 9 entfernt BinaryFormatter aus der .NET-Laufzeit. Die APIs sind weiterhin vorhanden, aber ihre Implementierungen lösen unabhängig vom Projekttyp immer eine Ausnahme aus. Weitere Informationen über das Entfernen und Ihre Optionen, wenn Sie betroffen sind, finden Sie im BinaryFormatter-Migrationshandbuch.
Sammlungen
Die Sammlungstypen in .NET erhalten die folgenden Updates für .NET 9:
- Collection-Lookups mit Spans
OrderedDictionary<TKey, TValue>- Mit der PriorityQueue.Remove()-Methode können Sie die Priorität eines Elements in der Warteschlange aktualisieren.
ReadOnlySet<T>
Collection-Lookups mit Spans
In hochperformantem Code werden Spans oft verwendet, um die unnötige Zuweisung von Zeichenfolgen zu vermeiden, und Nachschlagetabellen mit Typen wie Dictionary<TKey,TValue> und HashSet<T> werden häufig als Zwischenspeicher verwendet. Bisher gab es jedoch keinen sicheren, integrierten Mechanismus für die Suche in diesen Collection-Typen mit Spans. Mit der neuen allows ref struct Funktion in C# 13 und den neuen Funktionen für diese Sammlungsarten in .NET 9 ist es jetzt möglich, diese Arten von Nachschlagevorgängen auszuführen.
Das folgende Beispiel zeigt die Verwendung von Dictionary<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>
In vielen Szenarien möchte man möglicherweise Schlüssel-Wert-Paare so speichern, dass die Reihenfolge beibehalten werden kann (eine Liste von Schlüssel-Wert-Paaren), aber auch eine schnelle Suche nach Schlüsseln unterstützt wird (ein Wörterbuch mit Schlüssel-Wert-Paaren). Seit den frühen Tagen von .NET unterstützt der OrderedDictionary Typ dieses Szenarios, jedoch nur auf nicht generische Weise, mit Schlüsseln und Werten, die als objecteingegeben wurden. .NET 9 führt die lange angeforderte OrderedDictionary<TKey,TValue> Auflistung ein, die einen effizienten, generischen Typ zur Unterstützung dieser Szenarien bietet.
Der folgende Code verwendet die neue Klasse.
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() -Methode
.NET 6 hat die PriorityQueue<TElement,TPriority> Auflistung eingeführt, die eine einfache und schnelle Array-Heap-Implementierung bereitstellt. Ein Problem mit Array heaps im Allgemeinen besteht darin, dass sie keine Prioritätsupdates unterstützen, wodurch sie für die Verwendung in Algorithmen wie Variationen des Dijkstra-Algorithmus unertraglich sind.
Obwohl es nicht möglich ist, effiziente $O(\log n)$-Prioritätsupdates in der vorhandenen Sammlung zu implementieren, ermöglicht die neue PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) Methode das Emulieren von Prioritätsupdates (wenn auch bei $O(n)$ Zeit):
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);
}
Diese Methode entsperrt Benutzer, die Graphalgorithmen in Kontexten implementieren möchten, in denen die asymptotische Leistung kein Blocker ist. (Solche Kontexte umfassen Bildung und Prototyperstellung.) Hier ist beispielsweise eine Toy-Implementierung des Dijkstra-Algorithmus , der die neue API verwendet.
ReadOnlySet<T>
Es ist oft wünschenswert, schreibgeschützte Sichten auf Collections auszugeben. ReadOnlyCollection<T> ermöglicht es Ihnen, einen schreibgeschützten Wrapper um einen beliebigen mutierbaren IList<T> zu erstellen, und ReadOnlyDictionary<TKey,TValue> ermöglicht es Ihnen, einen schreibgeschützten Wrapper um einen beliebigen mutierbaren IDictionary<TKey,TValue> zu erstellen. In früheren Versionen von .NET gab es jedoch keine integrierte Unterstützung für die gleiche Vorgehensweise.ISet<T> .NET 9 führt ReadOnlySet<T> ein, um dies zu adressieren.
Die neue Klasse ermöglicht das folgende Verwendungsmuster.
private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;
public ReadOnlySet<int> Set => _setWrapper ??= new(_set);
Komponentenmodell - TypeDescriptor Unterstützung für das Trimmen
System.ComponentModel enthält neue opt-in trimmer-kompatible APIs für die Beschreibung von Komponenten. Jede Anwendung, insbesondere in sich geschlossene, getrimmte Anwendungen, können diese neuen APIs zur Unterstützung von Trimming-Szenarien verwenden.
Die primäre API ist die TypeDescriptor.RegisterType Methode für die TypeDescriptor Klasse. Diese Methode weist das DynamicallyAccessedMembersAttribute Attribut auf, sodass der Trimmer Elemente für diesen Typ behält. Sie sollten diese Methode einmal pro Typ und in der Regel frühzeitig aufrufen.
Die sekundären APIs weisen ein FromRegisteredType-Suffix auf, wie z.B. TypeDescriptor.GetPropertiesFromRegisteredType(Type). Im Gegensatz zu ihren Gegenstücken, die nicht über das FromRegisteredType Suffix verfügen, verfügen diese APIs nicht über [RequiresUnreferencedCode] Attribute oder [DynamicallyAccessedMembers] Trimmerattribute. Das Fehlen von Trimmer-Attributen kommt den Consumern zugute, da sie diese nicht mehr benötigen:
- Unterdrücken Sie Kürzungswarnungen, die riskant sein können.
- Einen stark typisierten
Type-Parameter an andere Methoden weiterzugeben, was umständlich oder nicht praktikabel sein kann.
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; }
}
Weitere Informationen finden Sie im API-Vorschlag.
Kryptographie
- CryptographicOperations.HashData() -Methode
- KMAC-Algorithmus
- AES-GCM- und ChaChaPoly1305-Algorithmen, die für iOS/tvOS/MacCatalyst aktiviert sind
- Laden von X.509-Zertifikaten
- OpenSSL-Anbieter unterstützen
- Windows CNG-virtualisierungsbasierte Sicherheit
CryptographicOperations.HashData() -Methode
.NET enthält mehrere statische "einmalige" Implementierungen von Hashfunktionen und damit verbundenen Funktionen. Diese APIs umfassen SHA256.HashData und HMACSHA256.HashData. One-Shot-APIs sind vorzugsweise zu verwenden, da sie die bestmögliche Leistung bieten und Zuordnungen reduzieren oder beseitigen können.
Wenn ein Entwickler eine API bereitstellen möchte, die Hashing unterstützt, bei der der Aufrufer definiert, welcher Hashalgorithmus verwendet werden soll, erfolgt dies in der Regel durch Akzeptieren eines HashAlgorithmName Arguments. Die Verwendung dieses Musters mit One-Shot-APIs erfordert jedoch die Umstellung aller möglichen HashAlgorithmName und dann die Verwendung der entsprechenden Methode. Um dieses Problem zu beheben, führt .NET 9 die CryptographicOperations.HashData API ein. Mit dieser API können Sie einen Hash oder einen HMAC über eine Eingabe in einem einzigen Schritt erstellen, wobei der verwendete Algorithmus von einem HashAlgorithmName bestimmt wird.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
KMAC-Algorithmus
.NET 9 stellt den KMAC-Algorithmus bereit, wie durch NIST SP-800-185 angegeben. KECCAK Message Authentication Code (KMAC) ist eine pseudozufällige Funktion und schlüsselabhängige Hash-Funktion basierend auf KECCAK.
Die folgenden neuen Klassen verwenden den KMAC-Algorithmus. Verwenden Sie Instanzen zum Sammeln von Daten, um einen MAC zu erzeugen, oder verwenden Sie die statische HashData-Methode für einen One-Shot über eine einzelne Eingabe.
KMAC ist unter Linux mit OpenSSL 3.0 oder höher und unter Windows 11 Build 26016 oder höher verfügbar. Mithilfe der statischen IsSupported Eigenschaft können Sie ermitteln, ob die Plattform den gewünschten Algorithmus unterstützt.
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- und ChaChaPoly1305-Algorithmen, die für iOS/tvOS/MacCatalyst aktiviert sind
IsSupported und ChaChaPoly1305.IsSupported gibt jetzt true zurück, wenn sie unter iOS 13+, tvOS 13+ und Mac Catalyst ausgeführt wird.
AesGcm unterstützt nur 16-Byte-Tagwerte (128-Bit) auf Apple-Betriebssystemen.
Laden von X.509-Zertifikaten
Seit .NET Framework 2.0 hat sich die Methode zum Laden eines Zertifikats geändert: new X509Certificate2(bytes). Es gab auch andere Muster wie new X509Certificate2(bytes, password, flags), , new X509Certificate2(path), new X509Certificate2(path, password, flags)und X509Certificate2Collection.Import(bytes, password, flags) (und seine Überladungen).
Diese Methoden nutzen alle Content-Sniffing, um herauszufinden, ob die Eingabe etwas ist, das sie verarbeiten können, und laden sie dann, wenn dies der Fall ist. Für einige Anrufer war diese Strategie sehr bequem. Aber es hat auch einige Probleme:
- Nicht jedes Dateiformat funktioniert auf jedem Betriebssystem.
- Es ist eine Protokollabweichung.
- Es ist eine Quelle von Sicherheitsproblemen.
.NET 9 führt eine neue X509CertificateLoader Klasse ein, die über eine "eine Methode, einen Zweck" verfügt. In der ursprünglichen Version unterstützt sie nur zwei der fünf Formate, die der X509Certificate2 Konstruktor unterstützt. Dies sind die beiden Formate, die auf allen Betriebssystemen funktioniert haben.
Unterstützung für OpenSSL-Provider
.NET 8 führte die OpenSSL-spezifischen APIs OpenPrivateKeyFromEngine(String, String) und OpenPublicKeyFromEngine(String, String) ein. Sie ermöglichen die Interaktion mit OpenSSL-Komponenten ENGINE und verwenden beispielsweise Hardwaresicherheitsmodule (HSM).
.NET 9 führt SafeEvpPKeyHandle.OpenKeyFromProvider(String, String) ein, das die Verwendung von OpenSSL-Anbietern ermöglicht und die Interaktion mit Anbietern wie tpm2 oder pkcs11 erlaubt.
Einige Distributionen haben die Unterstützung entferntENGINE, weil sie nun veraltet ist.
Der folgende Codeausschnitt zeigt die grundlegende Verwendung:
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.
}
Während des TLS-Handshakes gibt es einige Leistungsverbesserungen, sowie Verbesserungen bei der Interaktion mit RSA-Privatschlüsseln, die ENGINE Komponenten verwenden.
Windows CNG Virtualisierung-basierte Sicherheit
Windows 11 hat neue APIs hinzugefügt, um Windows-Schlüssel mit virtualisierungsbasierter Sicherheit (VBS) zu sichern. Mit dieser neuen Funktion können Schlüssel vor Schlüsseldiebstahlangriffen auf Administratorebene geschützt werden, und zwar mit geringem Einfluss auf Leistung, Zuverlässigkeit oder Skalierung.
.NET 9 hat übereinstimmende CngKeyCreationOptions Flags hinzugefügt. Die folgenden drei Flags wurden hinzugefügt:
-
CngKeyCreationOptions.PreferVbsentsprichtNCRYPT_PREFER_VBS_FLAG -
CngKeyCreationOptions.RequireVbsentsprichtNCRYPT_REQUIRE_VBS_FLAG -
CngKeyCreationOptions.UsePerBootKeyentsprichtNCRYPT_USE_PER_BOOT_KEY_FLAG
Der folgende Codeausschnitt veranschaulicht die Verwendung eines der Flags:
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.
}
Datum und Uhrzeit - neue TimeSpan.From* Überladungen
Die TimeSpan Klasse bietet mehrere From* Methoden an, mit denen Sie ein TimeSpan Objekt mit einem double erstellen können. Da es double sich jedoch um ein binärbasiertes Gleitkommaformat handelt, kann die inhärente Ungenauigkeit zu Fehlern führen. Beispielsweise könnte TimeSpan.FromSeconds(101.832) möglicherweise nicht genau 101 seconds, 832 milliseconds darstellen, sondern eher ungefähr 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds. Diese Diskrepanz hat häufige Verwirrung verursacht, und es ist auch nicht die effizienteste Möglichkeit, solche Daten darzustellen. Um dies zu beheben, fügt .NET 9 neue Überladungen hinzu, mit denen Sie Objekte aus ganzzahligen Zahlen erstellen TimeSpan können. Es gibt neue Überladungen von FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds und FromMicroseconds.
Der folgende Code zeigt ein Beispiel für den Aufruf der double und einer der neuen Integer-Überladungen.
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
Dependency Injection - ActivatorUtilities.CreateInstance Konstruktor
Die Konstruktorauflösung für ActivatorUtilities.CreateInstance hat sich in .NET 9 geändert. Zuvor wird ein Konstruktor, der explizit mit dem ActivatorUtilitiesConstructorAttribute Attribut gekennzeichnet wurde, je nach Reihenfolge von Konstruktoren und der Anzahl der Konstruktorparameter nicht aufgerufen. Die Logik hat sich in .NET 9 geändert, sodass ein Konstruktor, der das Attribut besitzt, immer aufgerufen wird.
Diagnostik
- Debug.Assert liefert standardmäßig die Assert-Bedingung
- Neue Activity.AddLink Methode
- Metrics.Gauge Instrument
- Out-of-proc Meter Platzhalter Listener
Debug.Assert liefert standardmäßig die Assert-Bedingung
Debug.Assert wird häufig verwendet, um die Überprüfung von Bedingungen zu unterstützen, die erwartet werden, dass sie immer wahr sind. Fehler deuten in der Regel auf einen Fehler im Code hin. Es gibt viele Überladungen von Debug.Assert, von denen die einfachste nur eine Bedingung akzeptiert.
Debug.Assert(a > 0 && b > 0);
Die Assertion schlägt fehl, wenn die Bedingung falsch ist. In der Vergangenheit waren solche Behauptungen jedoch ohne jegliche Informationen darüber, welche Bedingung fehlschlug. Ab .NET 9 enthält die Assertion die Textdarstellung der Bedingung, wenn vom Benutzer keine Nachricht explizit bereitgestellt wird. Für das vorangegangene Assert-Beispiel würden Sie zum Beispiel statt einer Nachricht wie:
Process terminated. Assertion failed.
at Program.SomeMethod(Int32 a, Int32 b)
Die Nachricht lautet jetzt:
Process terminated. Assertion failed.
a > 0 && b > 0
at Program.SomeMethod(Int32 a, Int32 b)
Neue Methode Activity.AddLink
Zuvor konnten Sie ein Tracing Activity nur mit anderen Tracing-Kontexten verknüpfen, wenn Sie das Activityerstellten. Neu in .NET 9 können Sie mit der AddLink(ActivityLink) API ein Activity Objekt mit anderen Ablaufverfolgungskontexten verknüpfen, nachdem es erstellt wurde. Diese Änderung entspricht auch den OpenTelemetry-Spezifikationen .
ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);
Activity activity = new("LinkTest");
activity.AddLink(activityLink);
Metrics.Gauge Instrument
System.Diagnostics.Metrics stellt nun das Gauge<T> Instrument gemäß der OpenTelemetry-Spezifikation bereit. Das Gauge Instrument dient zum Aufzeichnen nicht additiver Werte, wenn Änderungen auftreten. Beispielsweise kann der Hintergrundgeräuschpegel gemessen werden, bei dem das Addieren der Werte aus mehreren Räumen unsinnig wäre. Das Gauge-Instrument ist ein generischer Typ, der beliebige Werttypen aufzeichnen kann, wie beispielsweise int, double oder decimal.
Im folgenden Beispiel wird die Verwendung des Gauge Instruments veranschaulicht.
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" } });
Platzhalter für Out-of-proc Meter Listening
Es ist bereits möglich, mit dem System.Diagnostics.Metrics Event Source Anbieter auf Zähler außerhalb des Prozesses zu hören, aber vor .NET 9 mussten Sie den vollständigen Zählernamen angeben. In .NET 9 können Sie alle Meter mithilfe des Wildcardzeichens *abhören, mit dem Sie Metriken von jedem Meter in einem Prozess erfassen können. Darüber hinaus wurde Unterstützung für das Hören nach Meterpräfix hinzugefügt, sodass Sie alle Meter hören können, deren Namen mit einem angegebenen Präfix beginnen. Zum Beispiel ermöglicht das Angeben von MyMeter*, alle Meter abzuhören, deren Namen mit MyMeter beginnen.
// 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();
Die MyEventListener Klasse wird wie folgt definiert.
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]}");
}
}
}
Wenn Sie den Code ausführen, lautet die Ausgabe wie folgt:
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
Sie können auch das Platzhalterzeichen verwenden, um Metriken mit Monitoring Tools wie dotnet-counters abzuhören.
LINQ
Neue Methoden CountBy und AggregateBy wurden eingeführt. Diese Methoden ermöglichen es, den Zustand nach Schlüssel zu aggregieren, ohne dass Zwischengruppierungen über GroupBy erforderlich sind.
CountBy ermöglicht es Ihnen, die Häufigkeit der einzelnen Tasten schnell zu berechnen. Im folgenden Beispiel wird das Wort gefunden, das am häufigsten in einer Textzeichenfolge auftritt.
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 Mit dieser Option können Sie allgemeinere Workflows implementieren. Das folgende Beispiel zeigt, wie Sie Bewertungen berechnen können, die einem bestimmten Schlüssel zugeordnet sind.
(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>) ermöglicht es, den impliziten Index eines aufzählbaren Inhalts schnell zu extrahieren. Sie können jetzt Code wie den folgenden Codeausschnitt schreiben, um Elemente in einer Auflistung automatisch indizieren zu können.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Generator für Protokollierungsquellen
In C# 12 wurden primäre Konstruktoren eingeführt, mit denen Sie einen Konstruktor direkt in der Klassendeklaration definieren können. Der Protokollierungsquellgenerator unterstützt jetzt die Protokollierung mithilfe von Klassen mit einem primären Konstruktor.
public partial class ClassWithPrimaryConstructor(ILogger logger)
{
[LoggerMessage(0, LogLevel.Debug, "Test.")]
public partial void Test();
}
Verschiedenes
In diesem Abschnitt finden Sie Informationen zu:
allows ref struct in Bibliotheken verwendet
In C# 13 wird die Möglichkeit eingeführt, einen generischen Parameter mit allows ref struct einzuschränken, wodurch dem Compiler und zur Laufzeit mitgeteilt wird, dass ein ref struct für diesen generischen Parameter verwendet werden kann. Viele APIs, die damit kompatibel sind, wurden jetzt mit Anmerkungen versehen. Die String.Create-Methode verfügt beispielsweise über eine Überladung, mit der Sie ein string erstellen können, indem Sie direkt in seinen Speicher schreiben, dargestellt als Span. Diese Methode hat ein TState Argument, das vom Aufrufer an den Delegaten übergeben wird, der das eigentliche Schreiben ausführt.
Der Typparameter TState bei String.Create ist jetzt mit allows ref struct annotiert.
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
where TState : allows ref struct;
Mit dieser Anmerkung können Sie eine Spanne (oder ein anderes Element, zum Beispiel ref struct) als Eingabe an diese Methode übergeben.
Das folgende Beispiel zeigt eine neue String.ToLowerInvariant() Überladung, die diese Funktionalität nutzt.
public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));
SearchValues Erweiterung
.NET 8 hat den SearchValues<T> Typ eingeführt, der eine optimierte Lösung für die Suche nach bestimmten Zeichengruppen oder Byte innerhalb von Spannen bereitstellt. In .NET 9 wurde SearchValues erweitert, um die Suche nach Teilzeichenfolgen innerhalb einer größeren Zeichenfolge zu unterstützen.
Das folgende Beispiel sucht nach mehreren Tiernamen innerhalb einer Zeichenfolge und gibt einen Index für den ersten gefundenen zurück.
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);
Diese neue Funktion verfügt über eine optimierte Implementierung, die die SIMD-Unterstützung in der zugrunde liegenden Plattform nutzt. Es ermöglicht auch die Optimierung von Typen auf höherer Ebene. Zum Beispiel nutzt Regex jetzt diese Funktionalität als Teil seiner Implementierung.
Vernetzung
- SocketsHttpHandler ist in HttpClientFactory standardmäßig
- System.Net.ServerSentEvents
- TLS-Wiederaufnahme mit Client-Zertifikaten unter Linux
- WebSocket keep-alive ping und timeout
- HttpClientFactory protokolliert keine Headerwerte mehr standardmäßig
SocketsHttpHandler ist in HttpClientFactory standardmäßig
HttpClientFactory erstellt HttpClient Objekte, die standardmäßig von HttpClientHandler gesichert werden.
HttpClientHandler wird selbst unterstützt von SocketsHttpHandler, was viel konfigurierbarer ist, einschließlich der Verwaltung der Verbindungslebensdauer.
HttpClientFactory verwendet jetzt standardmäßig SocketsHttpHandler und konfiguriert es so, dass Grenzwerte für die Verbindungslebensdauer festgelegt werden, um mit der im Werk angegebenen Rotationslebensdauer übereinzustimmen.
System.Net.ServerSentEvents
Server sent events (SSE) ist ein einfaches und beliebtes Protokoll zum Streamen von Daten von einem Server zu einem Client. Es wird z.B. von OpenAI als Element des Streaming generierter Texte aus seinen KI-Diensten verwendet. Um die Nutzung von SSE zu vereinfachen, bietet die neue System.Net.ServerSentEvents Bibliothek einen Parser zum einfachen Aufnehmen von servergesendeten Ereignissen.
Der folgende Code veranschaulicht die Verwendung der neuen Klasse.
Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
Console.WriteLine(e.Data);
}
TLS-Wiederaufnahme mit Clientzertifikaten unter Linux
DER TLS-Lebenslauf ist ein Feature des TLS-Protokolls, mit dem zuvor erstellte Sitzungen auf einem Server fortgesetzt werden können. Dadurch werden einige Roundtrips vermieden und die Rechenressourcen während des TLS-Handshakes gespart.
Der TLS-Lebenslauf wurde bereits unter Linux für SslStream-Verbindungen ohne Clientzertifikate unterstützt. .NET 9 fügt Unterstützung für TLS-Resume von gegenseitig authentifizierten TLS-Verbindungen hinzu, die in Server-zu-Server-Szenarien üblich sind. Das Feature ist automatisch aktiviert.
WebSocket keep-alive ping und timeout
Neue APIs auf ClientWebSocketOptions und WebSocketCreationOptions bieten die Möglichkeit, sich für das Senden von WebSocket-Pings zu entscheiden und die Verbindung abzubrechen, wenn der Peer nicht rechtzeitig antwortet.
Bisher konnten Sie eine KeepAliveInterval Angabe angeben, um die Verbindung aktiv zu halten, aber gab es keinen eingebauten Mechanismus, um sicherzustellen, dass der Peer reagiert.
Im folgenden Beispiel wird der Server alle 5 Sekunden gepingt und die Verbindung abgebrochen, wenn sie nicht innerhalb einer Sekunde reagiert.
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 protokolliert keine Headerwerte mehr standardmäßig
LogLevel.Trace-Ereignisse, die von HttpClientFactory protokolliert werden, enthalten standardmäßig keine Headerwerte mehr. Sie können sich für die Protokollierung von Werten für bestimmte Header über die RedactLoggedHeaders Hilfsmethode entscheiden.
Im folgenden Beispiel werden alle Header mit Ausnahme des Benutzer-Agents redigiert.
services.AddHttpClient("myClient")
.RedactLoggedHeaders(name => name != "User-Agent");
Weitere Informationen finden Sie unter HttpClientFactory protokolliert standardmäßig nur gekürzte Header-Werte.
Spiegelung
Persistierte Assemblys
In .NET Core-Versionen und .NET 5-8 wurde die Unterstützung für das Erstellen einer Assembly und das Ausgeben von Spiegelungsmetadaten für dynamisch erstellte Typen auf eine runnable AssemblyBuilderbeschränkt. Der Mangel an Unterstützung für das Speichern einer Assembly war häufig ein Blocker für Kunden, die von .NET Framework zu .NET migrieren. .NET 9 fügt einen neuen Typ hinzu, PersistedAssemblyBuildermit dem Sie eine ausgegebene Assembly speichern können.
Rufen Sie zum Erstellen einer PersistedAssemblyBuilder Instanz den Konstruktor auf, und übergeben Sie den Assemblynamen, die Kernassembly, System.Private.CoreLibum auf Basislaufzeittypen und optionale benutzerdefinierte Attribute zu verweisen. Nachdem Sie alle Member an die Assembly ausgegeben haben, rufen Sie die PersistedAssemblyBuilder.Save(String) Methode auf, um eine Assembly mit Standardeinstellungen zu erstellen. Wenn Sie den Einstiegspunkt oder andere Optionen festlegen möchten, können Sie die zurückgegebenen Metadaten aufrufen PersistedAssemblyBuilder.GenerateMetadata und verwenden, um die Assembly zu speichern. Der folgende Code zeigt ein Beispiel zum Erstellen einer dauerhaften Assembly und Festlegen des Einstiegspunkts.
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]));
}
Die neue PersistedAssemblyBuilder Klasse enthält PDB-Unterstützung. Sie können Symbolinformationen ausgeben und zum Debuggen einer generierten Assembly verwenden. Die API hat eine ähnliche Form wie die .NET Framework-Implementierung. Weitere Informationen finden Sie unter Emit-Symbole und Generieren von PDB.
Typ-Namen-Parsing
TypeName ist ein Parser für ECMA-335-Typnamen, der eine ähnliche Funktionalität wie System.Type bietet, jedoch von der Laufzeitumgebung entkoppelt ist. Komponenten wie Serialisierer und Compiler müssen Namen von Prozesstypen analysieren und verarbeiten. Der native AOT-Compiler hat zum Beispiel auf die Verwendung von TypeName umgestellt.
Die neue TypeName Klasse bietet Folgendes:
Statische
ParseundTryParseMethoden für die Analyse von Eingaben, dargestellt alsReadOnlySpan<char>. Beide Methoden akzeptieren eine Instanz derTypeNameParseOptionsKlasse (ein Optionsbehälter), mit der Sie die Analyse anpassen können.Name,FullNameundAssemblyQualifiedNameEigenschaften, die genau wie ihre Gegenstücke funktionieren in System.Type.Mehrere Eigenschaften und Methoden, die zusätzliche Informationen zum Namen selbst bereitstellen:
-
IsArray,IsSZArray(SZsteht für eindimensionales, nullindiziertes Array),IsVariableBoundArrayType, undGetArrayRankfür die Arbeit mit Arrays. -
IsConstructedGenericType,GetGenericTypeDefinitionundGetGenericArgumentszum Arbeiten mit generischen Typnamen. -
IsByRefundIsPointerzum Arbeiten mit Zeigern und verwalteten Verweisen. -
GetElementType()zum Arbeiten mit Zeigern, Verweisen und Arrays. -
IsNestedundDeclaringTypezum Arbeiten mit geschachtelten Typen. -
AssemblyName, wodurch die Assemblynameninformationen über die neue AssemblyNameInfo Klasse verfügbar gemacht werden. Im Gegensatz dazuAssemblyNameist der neue Typ unveränderlich, und das Analysieren von Kulturnamen erstellt keine Instanzen vonCultureInfo.
-
Beide Typen TypeName und AssemblyNameInfo sind unveränderlich und bieten keine Möglichkeit, auf Gleichheit zu überprüfen (sie implementieren IEquatable nicht). Der Vergleich von Assemblynamen ist einfach, aber unterschiedliche Szenarien müssen nur eine Teilmenge der verfügbar gemachten Informationen (Name, Version, und CultureNamePublicKeyOrToken) vergleichen.
Der folgende Codeausschnitt zeigt einige Beispielverwendungen.
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()}'");
}
}
Die neuen APIs sind über das System.Reflection.Metadata NuGet-Paket verfügbar, das mit .NET-Versionen auf down-Level verwendet werden kann.
Reguläre Ausdrücke
[GeneratedRegex] auf Eigenschaften
.NET 7 hat den Regex Quellgenerator und das entsprechende GeneratedRegexAttribute Attribut eingeführt.
Die folgende partielle Methode wird quellenbasiert mit dem gesamten Code generiert, der zur Implementierung dieses Regex erforderlich ist.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();
C# 13 unterstützt partielle Eigenschaften zusätzlich zu partiellen Methoden, sodass Sie ab .NET 9 auch für eine Eigenschaft verwenden [GeneratedRegex(...)] können.
Die folgende partielle Eigenschaft ist das Äquivalent des vorherigen Beispiels.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }
Regex.EnumerateSplits
Die Regex Klasse stellt eine Split Methode bereit, ähnlich wie bei der String.Split Methode. Mit String.Split geben Sie ein oder mehrere char oder string-Trennzeichen an, und die Implementierung teilt den Eingabetext an diesen Trennzeichen auf. Statt das Trennzeichen als Regex.Split oder char zu spezifizieren, wird es mit string als reguläres Ausdrucksmuster angegeben.
Das folgende Beispiel veranschaulicht 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 Akzeptiert jedoch nur eine string Eingabe als Eingabe und unterstützt keine Eingabe als eine ReadOnlySpan<char>. Außerdem wird das gesamte Set von Splits als string[] ausgegeben, was die Zuweisung sowohl des string-Arrays zur Aufnahme der Ergebnisse als auch eines string für jeden Split erfordert. In .NET 9 ermöglicht die neue EnumerateSplits Methode das Ausführen desselben Vorgangs, jedoch mit einer spanbasierten Eingabe und ohne Zuweisung der Ergebnisse. Sie akzeptiert eine ReadOnlySpan<char> und gibt eine Aufzählung von Range Objekten zurück, die die Ergebnisse darstellen.
Das folgende Beispiel veranschaulicht Regex.EnumerateSplits, indem eine ReadOnlySpan<char> als Eingabe verwendet wird.
ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"Split: \"{input[r]}\"");
}
Serialisierung (System.Text.Json)
- Einrückungsoptionen
- Standardweboptionen singleton
- JsonSchemaExporter
- Nullbare Anmerkungen respektieren
- Erfordern von nicht optionalen Konstruktorparametern
- Order JsonObject-Eigenschaften
- Anpassen der Namen von Enum-Mitgliedern
- Streamen mehrerer JSON-Dokumente
Einzugsoptionen
JsonSerializerOptions enthält neue Eigenschaften, mit denen Sie das Einzugszeichen und die Einzugsgröße von geschriebener JSON anpassen können.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Standardweboptionen singleton
Wenn Sie mit den Standardoptionen serialisieren möchten, die ASP.NET Core für Web-Apps verwendet, verwenden Sie das neue JsonSerializerOptions.Web-Singleton.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
JsonSchemaExporter
JSON wird häufig verwendet, um Typen in Methodensignaturen als Teil von Remoteprozeduraufrufschemas darzustellen. Sie wird beispielsweise als Teil von OpenAPI-Spezifikationen oder als Teil des Toolaufrufs mit KI-Diensten wie openAI verwendet. Entwickler können .NET-Typen mithilfe von System.Text.Json als JSON serialisieren und deserialisieren. Sie müssen aber auch in der Lage sein, ein JSON-Schema abzurufen, das die Form des .NET-Typs beschreibt (d. h. die Form der Serialisierung und was deserialisiert werden kann). System.Text.Json stellt nun den Typ JsonSchemaExporter bereit, der das Generieren eines JSON-Schemas unterstützt, das einen .NET-Typ darstellt.
Weitere Informationen finden Sie unter JSON-Schemaexportierer.
Nullable-Anmerkungen berücksichtigen
System.Text.Json erkennt nun Anmerkungen zur Nullierbarkeit von Eigenschaften und kann so konfiguriert werden, dass diese während der Serialisierung und Deserialisierung mithilfe des RespectNullableAnnotations Flags erzwungen werden.
Der folgende Code zeigt, wie die Option festgelegt wird:
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; }
}
Weitere Informationen finden Sie unter Respect nullable annotations.
Erforderliche nicht-optionale Konstruktorparameter
Historisch hat System.Text.Json nicht-optionale Konstruktorparameter bei der Verwendung der konstruktorbasierten Deserialisierung als optional behandelt. Sie können dieses Verhalten mithilfe des neuen RespectRequiredConstructorParameters Flags ändern.
Der folgende Code zeigt, wie die Option festgelegt wird:
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);
Der MyPoco-Typ ist wie folgt definiert:
record MyPoco(string Value);
Weitere Informationen finden Sie unter Nicht optionalen Konstruktorparametern.
JsonObject-Eigenschaften anordnen
Der JsonObject Typ macht jetzt sortierte Wörterbuch-ähnliche APIs verfügbar, die explizite Bearbeitung der Eigenschaftsreihenfolge ermöglichen.
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
Weitere Informationen finden Sie unter Bearbeiten der Eigenschaftsreihenfolge.
Enum-Mitgliedsnamen anpassen
Das neue System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute Attribut kann verwendet werden, um die Namen einzelner Enumerationselemente für Typen anzupassen, die als Zeichenfolgen serialisiert werden:
JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"
[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
Weitere Informationen finden Sie unter Benutzerdefinierten Enumerationsmember-Namen.
Streamen mehrerer JSON-Dokumente
System.Text.Json.Utf8JsonReader unterstützt jetzt das Lesen mehrerer, leerzeichentrennten JSON-Dokumente aus einem einzelnen Puffer oder Datenstrom. Standardmäßig löst der Reader eine Ausnahme aus, wenn er Nicht-Whitespace-Zeichen am Ende des ersten Top-Level-Dokuments feststellt. Sie können dieses Verhalten mithilfe der AllowMultipleValues Kennzeichnung ändern.
Weitere Informationen finden Sie unter Lesen mehrerer JSON-Dokumente.
Spannweiten
Bei leistungsstarkem Code werden Spannweiten häufig verwendet, um unnötiges Zuordnen von Zeichenfolgen zu vermeiden. Span<T> und ReadOnlySpan<T> revolutionieren weiterhin, wie Code in .NET geschrieben wird, und mit jeder Version werden immer mehr Methoden hinzugefügt, die auf Spans arbeiten. .NET 9 enthält die folgenden span-bezogenen Aktualisierungen:
- Dateihilfsprogramme
-
params ReadOnlySpan<T>Überladungen - Auflisten über ReadOnlySpan<char>.Split() Segmente
Dateihilfsprogramme
Die File Klasse verfügt jetzt über neue Helfer, um ReadOnlySpan<char>/ReadOnlySpan<byte> und ReadOnlyMemory<char>/ReadOnlyMemory<byte> einfach und direkt in Dateien zu schreiben.
Der folgende Code schreibt effizient ein ReadOnlySpan<char> in eine Datei.
ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);
Neue StartsWith<T>(ReadOnlySpan<T>, T) und EndsWith<T>(ReadOnlySpan<T>, T) Erweiterungsmethoden wurden auch für Spans hinzugefügt, wodurch es einfach ist zu testen, ob ein ReadOnlySpan<T> mit einem bestimmten T Wert beginnt oder endet.
Der folgende Code verwendet diese neuen Komfort-APIs.
ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false
params ReadOnlySpan<T> Überladungen
C# unterstützt immer das Markieren von Arrayparametern als params. Dieses Schlüsselwort ermöglicht eine vereinfachte Aufrufsyntax. Beispielsweise wird der zweite Parameter der String.Join(String, String[]) Methode mit paramsmarkiert. Sie können diese Überladung mit einem Array aufrufen oder die Werte einzeln übergeben:
string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");
Vor .NET 9 sendet der C#-Compiler, wenn Sie die Werte einzeln übergeben, code identisch mit dem ersten Aufruf, indem ein implizites Array um die drei Argumente erzeugt wird.
Ab C# 13 können Sie params mit jedem Argument verwenden, das über einen Ausdruck für eine Collection konstruiert werden kann, einschließlich Spans (Span<T> und ReadOnlySpan<T>). Das ist von Vorteil für Benutzerfreundlichkeit und Leistung. Der C#-Compiler kann die Argumente im Stapel speichern, eine Spanne um sie herum umschließen und an die Methode übergeben, wodurch die implizite Arrayzuordnung vermieden wird, die andernfalls entstanden wäre.
.NET 9 enthält über 60 Methoden mit einem params ReadOnlySpan<T> Parameter. Einige sind brandneue Überladungen und andere sind bestehende Methoden, die bereits ein ReadOnlySpan<T> angenommen haben, diesen Parameter aber jetzt mit params markiert haben. Der Nettoeffekt ist, wenn Sie ein Upgrade auf .NET 9 durchführen und Den Code neu kompilieren, werden Leistungsverbesserungen angezeigt, ohne Codeänderungen vorzunehmen. Der Grund dafür ist, dass der Compiler lieber eine Bindung an span-basierte Überladungen als an arraybasierte Überladungen vorzieht.
Zum Beispiel enthält String.Join jetzt die folgende Überladung, die das neue Muster implementiert: String.Join(String, ReadOnlySpan<String>)
Jetzt wird ein Aufruf wie string.Join(", ", "a", "b", "c") ausgeführt, ohne ein Array zuzuordnen, um die Argumente "a", "b" und "c" zu übergeben.
Auflisten über ReadOnlySpan<char>.Split() Segmente
string.Split ist eine bequeme Methode zum schnellen Partitionieren einer Zeichenfolge mit einem oder mehreren bereitgestellten Trennzeichen. Für Code, der sich auf die Leistung konzentriert, kann das Zuordnungsprofil string.Split jedoch unertragbar sein, da es eine Zeichenfolge für jede analysierte Komponente und eine string[] zum Speichern aller Komponenten zuweist. Es funktioniert auch nicht mit Spans. Wenn Sie also einen ReadOnlySpan<char> haben, sind Sie gezwungen, bei der Umwandlung in eine Zeichenfolge eine weitere Zeichenfolge zuzuweisen, um string.Split darauf aufrufen zu können.
In .NET 8 wurden eine Reihe von Split- und SplitAny-Methoden für ReadOnlySpan<char> eingeführt. Anstatt ein neues string[] zurückzugeben, akzeptieren diese Methoden ein Ziel Span<Range>, in das die Begrenzungsindizes für jede Komponente geschrieben werden. Dadurch wird der Vorgang völlig frei von Zuweisungen. Diese Methoden eignen sich für die Verwendung, wenn die Anzahl der Bereiche sowohl bekannt als auch klein ist.
In .NET 9 wurden neue Überladungen von Split und SplitAny hinzugefügt, um die Möglichkeit zu bieten, ein ReadOnlySpan<T> mit einer a priori unbekannten Anzahl von Segmenten schrittweise zu parsen. Die neuen Methoden ermöglichen das Aufzählen der einzelnen Segmente, die in ähnlicher Weise dargestellt werden als ein Range Segment, das zum Segmentieren in die ursprüngliche Spanne verwendet werden kann.
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.Formate
Die Position oder der Offset der Daten im eingeschlossenen Datenstrom für ein TarEntry Objekt ist jetzt eine öffentliche Eigenschaft. TarEntry.DataOffset gibt die Position im Archivdatenstrom des Eintrags zurück, an der sich das erste Datenbyte des Eintrags befindet. Die Daten des Eintrags sind in einem Substream gekapselt, auf den Sie über TarEntry.DataStream zugreifen können, wodurch die tatsächliche Position der Daten relativ zum Archiv-Stream verborgen bleibt. Das reicht für die meisten Benutzer aus, aber wenn Sie mehr Flexibilität benötigen und die echte Ausgangsposition der Daten im Archivdatenstrom kennen möchten, erleichtert die neue TarEntry.DataOffset API die Unterstützung von Features wie gleichzeitigem Zugriff mit sehr großen TAR-Dateien.
// 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() erstellt eine Guid, die größtenteils mit kryptografisch sicheren Zufallsdaten gefüllt ist, gemäß der Spezifikation der UUID Version 4 in RFC 9562. Das gleiche RFC definiert auch andere Versionen, einschließlich Version 7, die "ein zeitgeordnetes Wertfeld enthält, das von der weit implementierten und bekannten Unix Epoch-Zeitstempelquelle abgeleitet ist". Mit anderen Worten, ein Großteil der Daten ist immer noch zufällig, aber einige davon sind für Daten basierend auf einem Zeitstempel reserviert, wodurch diese Werte eine natürliche Sortierreihenfolge aufweisen können. In .NET 9 können Sie eine Guid entsprechend Version 7 über die neuen Guid.CreateVersion7() und Guid.CreateVersion7(DateTimeOffset) Methoden erstellen. Sie können auch die neue Version Eigenschaft verwenden, um das Versionsfeld eines Guid Objekts abzurufen.
System.IO
- Komprimierung mit zlib-ng
- ZLib- und Brotli-Komprimierungsoptionen
- XPS-Dokumente von einem virtuellen XPS-Drucker
Komprimierung mit zlib-ng
System.IO.Compression Features wie ZipArchive, DeflateStream, GZipStreamund ZLibStream alle basieren hauptsächlich auf der Zlib-Bibliothek. Ab .NET 9 verwenden diese Features stattdessen zlib-ng, eine Bibliothek, die eine konsistentere und effizientere Verarbeitung über ein breiteres Array von Betriebssystemen und Hardware hinweg liefert.
ZLib- und Brotli-Komprimierungsoptionen
ZLibCompressionOptions und BrotliCompressionOptions sind neue Typen zum Festlegen der algorithmusspezifischen Komprimierungsebene und -strategie (, Default, Filtered, HuffmanOnly, oder RunLengthEncoding). Diese Typen richten sich an Benutzer, die besser abgestimmte Einstellungen wünschen als die einzige vorhandene Option, <System.IO.Compression.CompressionLevel>.
Die neuen Komprimierungsoptionstypen können in Zukunft erweitert werden.
Der folgende Codeausschnitt zeigt einige Beispielverwendungen:
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-Dokumente von einem virtuellen XPS-Drucker
XPS-Dokumente, die von einem virtuellen V4 XPS-Drucker stammen, konnten zuvor nicht mithilfe der System.IO.Packaging Bibliothek geöffnet werden, da die Verarbeitung von .piece-Dateien fehlt. Diese Lücke wurde in .NET 9 behoben.
System.Numerik
- BigInteger Obergrenze
-
BigMulApis - Vektorkonvertierungs-APIs
- Vektorerstellungs-APIs
- Zusätzliche Beschleunigung
BigInteger Obergrenze
BigInteger unterstützt die Darstellung ganzzahliger Werte von im Wesentlichen beliebiger Länge. In der Praxis wird die Länge jedoch durch Grenzwerte des zugrunde liegenden Computers eingeschränkt, z. B. verfügbarer Arbeitsspeicher oder wie lange es dauert, einen bestimmten Ausdruck zu berechnen. Darüber hinaus gibt es einige APIs, die aufgrund von Eingaben fehlschlagen, die zu einem zu großen Wert führen. Aufgrund dieser Grenzwerte erzwingt .NET 9 eine maximale Länge von BigInteger, d. h., dass sie nicht mehr als (2^31) - 1 (ca. 2,14 Milliarden) Bits enthalten kann. Eine solche Zahl stellt eine Zuweisung von fast 256 MB dar und enthält ca. 646,5 Millionen Ziffern. Diese neue Grenze stellt sicher, dass alle APIs, die verfügbar gemacht werden, gut verhalten und konsistent sind und gleichzeitig Zahlen zulassen, die weit über die meisten Nutzungsszenarien hinausgehen.
BigMul Apis
BigMul ist ein Vorgang, der das gesamte Produkt von zwei Nummern erzeugt. .NET 9 fügt dedizierte BigMul APIs zu int, long, uint, und ulong deren Rückgabetyp der nächste größere ganzzahlige Typ ist als die Parametertypen.
Die neuen APIs sind:
-
BigMul(Int32, Int32) (gibt
longzurück) -
BigMul(Int64, Int64) (gibt
Int128zurück) -
BigMul(UInt32, UInt32) (gibt
ulongzurück) -
BigMul(UInt64, UInt64) (gibt
UInt128zurück)
Vektorkonvertierungs-APIs
.NET 9 fügt dedizierte Erweiterungs-APIs für die Konvertierung zwischen Vector2, Vector3, Vector4, Quaternion und Plane hinzu.
Die neuen APIs sind wie folgt:
- AsPlane(Vector4)
- AsQuaternion(Vector4)
- AsVector2(Vector4)
- AsVector3(Vector4)
- AsVector4(Plane)
- AsVector4(Quaternion)
- AsVector4(Vector2)
- AsVector4(Vector3)
- AsVector4Unsafe(Vector2)
- AsVector4Unsafe(Vector3)
Bei Konvertierungen in gleicher Größe, z. B. zwischen Vector4, Quaternion, und Plane, sind diese Konvertierungen Nullkosten. Das gleiche kann für verengte Konvertierungen gesagt werden, z. B. von Vector4 bis zu Vector2Vector3. Für die Erweiterung von Konvertierungen, z. B. von Vector2 oder Vector3 nach Vector4, gibt es die normale API, die neue Elemente auf 0 initialisiert, und eine Unsafe suffixierte API, die diese neuen Elemente nicht definiert und daher null Kosten haben kann.
Vektorerstellungs-APIs
Es gibt neue Create APIs, die für Vector, Vector2, Vector3 und Vector4 verfügbar gemacht werden und die den entsprechenden APIs der Hardwarevektortypen entsprechen, die im System.Runtime.Intrinsics Namespace verfügbar sind.
Weitere Informationen zu den neuen APIs finden Sie unter:
Diese APIs dienen in erster Linie der Bequemlichkeit und der allgemeinen Konsistenz in .NETs SIMD-beschleunigten Typen.
Zusätzliche Beschleunigung
Zusätzliche Leistungsverbesserungen wurden für viele Typen im System.Numerics Namespace vorgenommen, einschließlich zu BigInteger, , Vector2, Vector3, Vector4, Quaternionund Plane.
In einigen Fällen hat dies zu einer 2-5fachen Beschleunigung von Kern-APIs geführt, darunter die Matrix4x4 Multiplikation, die Erstellung von Plane aus einer Reihe von Vertices, die Quaternion Verkettung und die Berechnung des Kreuzprodukts eines Vector3.
Es gibt auch Unterstützung für Konstantenfaltung bei der SinCos-API, die sowohl Sin(x) als auch Cos(x) in einem einzigen Aufruf berechnet und dadurch effizienter ist.
Tensoren für KI
Tensoren sind die Eckpfeilerdatenstruktur der künstlichen Intelligenz (AI). Sie können oft als multidimensionale Arrays betrachtet werden.
Tensoren werden verwendet, um:
- Darstellen und Codieren von Daten wie Textsequenzen (Token), Bildern, Video und Audio.
- Effizientes Bearbeiten höherdimensionaler Daten.
- Effizientes Anwenden von Berechnungen auf höherdimensionale Daten.
- Speichern Von Gewichtsinformationen und Zwischenberechnungen (in neuralen Netzwerken).
Um die .NET-Tensor-APIs zu verwenden, installieren Sie das NuGet-Paket "System.Numerics.Tensors ".
Neuer Tensor<T-Typ>
Der neue Tensor<T> Typ erweitert die KI-Funktionen der .NET-Bibliotheken und -Laufzeit. Dieser Typ:
- Bietet effiziente Interoperabilität mit KI-Bibliotheken wie ML.NET, TorchSharp und ONNX Runtime unter Verwendung von Nullkopien, sofern möglich.
- Baut auf TensorPrimitives für effiziente mathematische Vorgänge auf.
- Ermöglicht eine einfache und effiziente Datenverarbeitung durch die Bereitstellung von Indizierungs- und Zerteilungsoperationen.
- Ist kein Ersatz für vorhandene KI- und Machine Learning-Bibliotheken. Stattdessen soll eine gemeinsame Gruppe von APIs bereitgestellt werden, um die Codeduplizierung und Abhängigkeiten zu reduzieren und mithilfe der neuesten Laufzeitfunktionen eine bessere Leistung zu erzielen.
Die folgenden Codes zeigen einige der APIs, die im neuen Tensor<T> Typ enthalten sind.
// 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]]
Hinweis
Diese API ist als experimentell für .NET 9 markiert.
TensorPrimitive
Die System.Numerics.Tensors Bibliothek enthält die TensorPrimitives Klasse, die statische Methoden zum Ausführen numerischer Vorgänge für Wertebereiche bereitstellt. In .NET 9 wurde der Umfang der von TensorPrimitives bereitgestellten Methoden erheblich erweitert, wobei die Anzahl von 40 (in .NET 8) auf fast 200 Methodenüberladungen stieg. Der Oberflächenbereich umfasst vertraute numerische Vorgänge aus Typen wie Math und MathF. Es enthält auch die generischen mathematischen Schnittstellen wie INumber<TSelf>, außer dass sie einen einzelnen Wert verarbeiten, sie verarbeiten eine Spanne von Werten. Viele Vorgänge wurden auch über SIMD-optimierte Implementierungen für .NET 9 beschleunigt.
TensorPrimitives macht jetzt generische Überladungen für jeden Typ T verfügbar, der eine bestimmte Schnittstelle implementiert. (Die .NET 8-Version enthält nur Überladungen zum Bearbeiten von Spannen von float Werten.) Beispielsweise führt die neue CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) Überladung die Kosinusähnlichkeit für zwei Vektoren von float, double oder Half Werten oder Werten eines anderen Typs durch, die IRootFunctions<TSelf> implementieren.
Vergleichen Sie die Präzision der Kosinus-Ähnlichkeit zwischen zwei Vektoren des Typs float und 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
Einfädelnd
Die Threading-APIs enthalten Verbesserungen für die Iteration durch Tasks, für priorisierte Kanäle, die ihre Elemente ordnen können, anstatt FIFO (first-in-first-out) zu sein, und Interlocked.CompareExchange für mehr Typen.
Task.WhenEach
Es wurden verschiedene hilfreiche neue APIs zum Arbeiten mit Task<TResult> Objekten hinzugefügt. Mit der neuen Task.WhenEach-Methode können Sie durch Aufgaben iterieren, wenn diese mit einer await foreach-Anweisung abgeschlossen sind. Sie müssen nicht mehr Dinge tun wie den wiederholten Aufruf von Task.WaitAny für ein Set von Aufgaben, um die nächste abzuschließende Aufgabe auszuwählen.
Der folgende Code führt mehrere HttpClient Aufrufe aus und bearbeitet deren Ergebnisse, sobald sie abgeschlossen sind.
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);
}
Priorisierter ungebundener Kanal
Mit dem System.Threading.Channels-Namespace können Sie FiFO-Kanäle (First-in-First-Out) mithilfe der CreateBounded und CreateUnbounded Methoden erstellen. Bei FIFO-Kanälen werden Elemente aus dem Kanal in der Reihenfolge gelesen, in der sie geschrieben wurden. In .NET 9 wurde die neue CreateUnboundedPrioritized Methode hinzugefügt, die die Elemente so anordnet, dass das nächste Element, das aus dem Kanal gelesen wird, der wichtigste ist, entsprechend einer Comparer<T>.Default oder einer benutzerdefinierten IComparer<T>.
Im folgenden Beispiel wird die neue Methode verwendet, um einen Kanal zu erstellen, der die Zahlen 1 bis 5 in der Reihenfolge ausgibt, obwohl sie in eine andere Reihenfolge in den Kanal geschrieben werden.
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 für mehr Typen
In früheren Versionen von .NET hatten Interlocked.Exchange und Interlocked.CompareExchange Überladungen, um mit int, uint, long, ulong, nint, nuint, float, double, und object zu arbeiten, sowie eine generische Überladung, um mit jedem Verweistyp T zu arbeiten. In .NET 9 gibt es neue Überladungen für das atomare Arbeiten mit byte, sbyte, short und ushort. Außerdem wurde die generische Einschränkung für die generischen Interlocked.Exchange<T> und Interlocked.CompareExchange<T> Überladungen entfernt, sodass diese Methoden nicht mehr darauf beschränkt sind, nur mit Verweistypen zu arbeiten. Sie können jetzt mit jedem primitiven Typ arbeiten, der alle oben genannten Typen sowie bool sowie charjeden Typ enum enthält.