Freigeben über


Neuerungen in .NET-Bibliotheken für .NET 10

In diesem Artikel werden neue Features in den .NET-Bibliotheken für .NET 10 beschrieben.

Kryptographie

Suchen nach Zertifikaten nach anderen Fingerabdrucken als SHA-1

Das Eindeutige Auffinden von Zertifikaten durch Fingerabdruck ist ein ziemlich häufiger Vorgang, aber die X509Certificate2Collection.Find(X509FindType, Object, Boolean) Methode (für den FindByThumbprint Modus) sucht nur nach dem SHA-1-Fingerabdruckwert.

Es besteht ein gewisses Risiko, die Find-Methode für die Suche nach SHA-2-256 ("SHA256") und SHA-3-256-Fingerabdrucken zu verwenden, da diese Hashalgorithmen dieselbe Länge aufweisen.

Stattdessen führt .NET 10 eine neue Methode ein, die den Namen des Hashalgorithmus akzeptiert, der für den Abgleich verwendet werden soll.

X509Certificate2Collection coll = store.Certificates.FindByThumbprint(HashAlgorithmName.SHA256, thumbprint);
Debug.Assert(coll.Count < 2, "Collection has too many matches, has SHA-2 been broken?");
return coll.SingleOrDefault();

Suchen von PEM-codierten Daten in ASCII/UTF-8

Die PEM-Codierung (ursprünglich Privacy Enhanced Mail, aber jetzt weit außerhalb der E-Mail verwendet) ist für "Text" definiert, was bedeutet, dass die PemEncoding Klasse für die Ausführung auf String und ReadOnlySpan<char>entwickelt wurde. Es ist jedoch üblich (insbesondere unter Linux), etwas wie ein Zertifikat in einer Datei zu haben, das die ASCII-Codierung (Zeichenfolge) verwendet. Historisch bedeutete dies, dass Sie die Datei öffnen und die Bytes in Zeichen (oder eine Zeichenfolge) konvertieren mussten, bevor Sie PemEncodingverwenden können.

Die neue PemEncoding.FindUtf8(ReadOnlySpan<Byte>) Methode nutzt die Tatsache, dass PEM nur für 7-Bit-ASCII-Zeichen definiert ist und dass 7-Bit-ASCII eine perfekte Überschneidung mit einzelbyte UTF-8-Werten aufweist. Durch Aufrufen dieser neuen Methode können Sie die UTF-8/ASCII-to-char-Konvertierung überspringen und die Datei direkt lesen.

byte[] fileContents = File.ReadAllBytes(path);
-char[] text = Encoding.ASCII.GetString(fileContents);
-PemFields pemFields = PemEncoding.Find(text);
+PemFields pemFields = PemEncoding.FindUtf8(fileContents);

-byte[] contents = Base64.DecodeFromChars(text.AsSpan()[pemFields.Base64Data]);
+byte[] contents = Base64.DecodeFromUtf8(fileContents.AsSpan()[pemFields.Base64Data]);

Verschlüsselungsalgorithmus für PKCS#12/PFX-Export

Mit den neuen ExportPkcs12-Methoden können X509Certificate-Aufrufer auswählen, welche Verschlüsselungs- und Digest-Algorithmen verwendet werden, um das Ergebnis zu erzeugen:

  • Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1 gibt den Windows XP-Era-Standard de facto an. Es erzeugt eine Ausgabe, die von fast jeder Bibliothek und Plattform unterstützt wird, die das Lesen von PKCS#12/PFX durch Auswählen eines älteren Verschlüsselungsalgorithmus unterstützt.
  • Pkcs12ExportPbeParameters.Pbes2Aes256Sha256 gibt an, dass AES anstelle von 3DES (und SHA-2-256 anstelle von SHA-1) verwendet werden soll, die Ausgabe kann jedoch von allen Lesern (z. B. Windows XP) nicht verstanden werden.

Wenn Sie noch mehr Kontrolle wünschen, können Sie die Überladung verwenden, die ein PbeParameters akzeptiert.

Post-Quantum-Kryptografie (PQC)

.NET 10 enthält Unterstützung für drei neue asymmetrische Algorithmen: ML-KEM (FIPS 203), ML-DSA (FIPS 204) und SLH-DSA (FIPS 205). Die neuen Typen sind:

Da sie wenig Nutzen bringt, leiten sich diese neuen Typen nicht von AsymmetricAlgorithm. Anstatt ein AsymmetricAlgorithm Objekt zu erstellen und dann einen Schlüssel darin zu importieren oder einen neuen Schlüssel zu generieren, verwenden alle neuen Typen statische Methoden, um einen Schlüssel zu generieren oder zu importieren:

using System;
using System.IO;
using System.Security.Cryptography;

private static bool ValidateMLDsaSignature(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, string publicKeyPath)
{
    string publicKeyPem = File.ReadAllText(publicKeyPath);

    using (MLDsa key = MLDsa.ImportFromPem(publicKeyPem))
    {
        return key.VerifyData(data, signature);
    }
}

Und anstatt Objekteigenschaften festzulegen und einen Schlüssel zu erstellen, erfordert die Schlüsselerzeugung bei diesen neuen Typen alle erforderlichen Optionen.

using (MLKem key = MLKem.GenerateKey(MLKemAlgorithm.MLKem768))
{
    string publicKeyPem = key.ExportSubjectPublicKeyInfoPem();
    ...
}

Diese Algorithmen setzen alle mit dem Muster fort, dass eine statische IsSupported Eigenschaft vorhanden ist, um anzugeben, ob der Algorithmus im aktuellen System unterstützt wird.

.NET 10 umfasst die Unterstützung der Windows-Kryptografie-API: Next Generation (CNG) für Post-Quantum Cryptography (PQC), die diese Algorithmen auf Windows-Systemen mit PQC-Unterstützung zur Verfügung stellt. Beispiel:

using System;
using System.IO;
using System.Security.Cryptography;

private static bool ValidateMLDsaSignature(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, string publicKeyPath)
{
    string publicKeyPem = File.ReadAllText(publicKeyPath);

    using MLDsa key = MLDsa.ImportFromPem(publicKeyPem);
    return key.VerifyData(data, signature);
}

Die PQC-Algorithmen sind auf Systemen verfügbar, auf denen die Systemgrafiebibliotheken OpenSSL 3.5 (oder neuer) oder Windows CNG mit PQC-Unterstützung sind. Der MLKem Typ ist nicht als [Experimental] gekennzeichnet, aber einige seiner Methoden sind es und bleiben es, bis die zugrunde liegenden Standards finalisiert sind. Die MLDsa, SlhDsa und CompositeMLDsa Klassen sind bis zur Fertigstellung der Entwicklung unter [Experimental] bei diagnostischen SYSLIB5006 gekennzeichnet.

ML-DSA

Die MLDsa Klasse enthält Benutzerfreundlichkeitsfeatures, die allgemeine Codemuster vereinfachen:

private static byte[] SignData(string privateKeyPath, ReadOnlySpan<byte> data)
{
    using (MLDsa signingKey = MLDsa.ImportFromPem(File.ReadAllBytes(privateKeyPath)))
    {
-       byte[] signature = new byte[signingKey.Algorithm.SignatureSizeInBytes];
-       signingKey.SignData(data, signature);
+       return signingKey.SignData(data);
-       return signature;
    }
}

Darüber hinaus fügt .NET 10 Unterstützung für HashML-DSA hinzu, die als "PreHash" bezeichnet wird, um es von "pure" ML-DSA zu unterscheiden. Da die zugrunde liegende Spezifikation mit dem Objektbezeichnerwert (Object Identifier, OID) interagiert, nehmen die Methoden SignPreHash und VerifyPreHash für diesen [Experimental] Typ die punktierte dezimale OID als Zeichenfolge. Dies kann sich entwickeln, da mehr Szenarien mit HashML-DSA gut definiert werden.

private static byte[] SignPreHashSha3_256(MLDsa signingKey, ReadOnlySpan<byte> data)
{
    const string Sha3_256Oid = "2.16.840.1.101.3.4.2.8";
    return signingKey.SignPreHash(SHA3_256.HashData(data), Sha3_256Oid);
}

Ab RC 1 unterstützt ML-DSA auch Signaturen, die von einem "externen" Mu-Wert erstellt und überprüft werden, was zusätzliche Flexibilität für erweiterte kryptografische Szenarien bietet:

private static byte[] SignWithExternalMu(MLDsa signingKey, ReadOnlySpan<byte> externalMu)
{
    return signingKey.SignMu(externalMu);
}

private static bool VerifyWithExternalMu(MLDsa verifyingKey, ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature)
{
    return verifyingKey.VerifyMu(externalMu, signature);
}

Zusammengesetzte ML-DSA

.NET 10 führt neue Typen zur Unterstützung von ietf-lamps-pq-composite-sigs (bei Entwurf 8 ab .NET 10 GA) ein, einschließlich der Typen CompositeMLDsa und CompositeMLDsaAlgorithm mit der Implementierung der Grundmethoden für RSA-Varianten.

var algorithm = CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss;
using var privateKey = CompositeMLDsa.GenerateKey(algorithm);

byte[] data = [42];
byte[] signature = privateKey.SignData(data);

using var publicKey = CompositeMLDsa.ImportCompositeMLDsaPublicKey(algorithm, privateKey.ExportCompositeMLDsaPublicKey());
Console.WriteLine(publicKey.VerifyData(data, signature)); // True

signature[0] ^= 1; // Tamper with signature
Console.WriteLine(publicKey.VerifyData(data, signature)); // False

AES-Schlüsselverpackung mit Polsterung (AES KeyWrap with Padding) gemäß IETF RFC 5649

AES-KWP ist ein Algorithmus, der gelegentlich in Konstruktionen wie Kryptografienachrichtensyntax (CMS) EnvelopedData verwendet wird, wobei Inhalte einmal verschlüsselt werden, aber der Entschlüsselungsschlüssel muss an mehrere Parteien verteilt werden, jedes in einer unterschiedlichen geheimen Form.

.NET unterstützt jetzt den AES-KWP Algorithmus über Instanzmethoden für die Aes Klasse:

private static byte[] DecryptContent(ReadOnlySpan<byte> kek, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.SetKey(kek);

        Span<byte> dek = stackalloc byte[256 / 8];
        int length = aes.DecryptKeyWrapPadded(encryptedKey, dek);

        aes.SetKey(dek.Slice(0, length));
        return aes.DecryptCbc(ciphertext);
    }
}

Globalisierung und Datum/Uhrzeit

Neue Methodenüberladungen in ISOWeek für den DateOnly-Typ

Die ISOWeek Klasse wurde ursprünglich entwickelt, um ausschließlich mit DateTimezu arbeiten, da sie eingeführt wurde, bevor der DateOnly Typ vorhanden war. Da nun DateOnly verfügbar ist, ist es sinnvoll, ISOWeek auch zu unterstützen. Die folgenden Überladungen sind neu:

Numerische Sortierung für den Zeichenfolgenvergleich

Numerischer Zeichenfolgenvergleich ist ein stark nachgefragtes Merkmal für den numerischen Vergleich von Zeichenfolgen anstelle des lexikografischen Vergleichs. Beispielsweise ist 2 kleiner als 10, daher sollte "2" vor "10" angezeigt werden, wenn die Reihenfolge numerisch erfolgt. Ebenso sind "2" und "02" numerisch gleich. Mit der neuen NumericOrdering Option ist es jetzt möglich, diese Arten von Vergleichen auszuführen:

StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);

Console.WriteLine(numericStringComparer.Equals("02", "2"));
// Output: True

foreach (string os in new[] { "Windows 8", "Windows 10", "Windows 11" }.Order(numericStringComparer))
{
    Console.WriteLine(os);
}

// Output:
// Windows 8
// Windows 10
// Windows 11

HashSet<string> set = new HashSet<string>(numericStringComparer) { "007" };
Console.WriteLine(set.Contains("7"));
// Output: True

Diese Option ist für die folgenden indexbasierten Zeichenfolgenvorgänge nicht gültig: IndexOf, LastIndexOf, StartsWith, EndsWith, IsPrefixund IsSuffix.

Neue TimeSpan.FromMilliseconds Überladung mit einem einzigen Parameter

Die TimeSpan.FromMilliseconds(Int64, Int64)-Methode wurde zuvor eingeführt, ohne eine Überladung hinzuzufügen, die einen einzelnen Parameter entgegennimmt.

Obwohl dies funktioniert, da der zweite Parameter optional ist, verursacht er einen Kompilierungsfehler, wenn er in einem LINQ-Ausdruck wie folgt verwendet wird:

Expression<Action> a = () => TimeSpan.FromMilliseconds(1000);

Das Problem tritt auf, da LINQ-Ausdrücke keine optionalen Parameter verarbeiten können. Um dies zu beheben, führt .NET 10 eine neue Überladung ein, die einen einzelnen Parameter annimmt. Außerdem wird die vorhandene Methode so geändert, dass der zweite Parameter obligatorisch ist.

Streichinstrumente

APIs zur Normalisierung von Zeichenfolgen für die Arbeit mit Span of characters

Unicode-Zeichenfolgennormalisierung wurde seit langem unterstützt, aber vorhandene APIs funktionieren nur mit dem Zeichenfolgentyp. Dies bedeutet, dass Aufrufer mit Daten, die in unterschiedlichen Formen gespeichert sind, z. B. Zeichenarrays oder Spannen, eine neue Zeichenfolge zuweisen müssen, um diese APIs zu verwenden. Darüber hinaus weisen APIs, die eine normalisierte Zeichenfolge zurückgeben, immer eine neue Zeichenfolge zur Darstellung der normalisierten Ausgabe zu.

.NET 10 führt neue APIs ein, die mit Zeichenspannen arbeiten, die die Normalisierung über Zeichenfolgentypen hinaus erweitern und dazu beitragen, unnötige Zuordnungen zu vermeiden:

UTF-8-Unterstützung für die Hex-Zeichenfolgenkonvertierung

.NET 10 fügt UTF-8-Unterstützung für Hex-Zeichenfolgenkonvertierungsvorgänge in der Convert Klasse hinzu. Diese neuen Methoden bieten effiziente Möglichkeiten zum Konvertieren zwischen UTF-8-Bytesequenzen und Hexadezimaldarstellungen, ohne dass Zwischenzeichenfolgenzuweisungen erforderlich sind:

Diese Methoden spiegeln die bestehenden Überladungen wider, die mit string und ReadOnlySpan<char> arbeiten, indem sie direkt auf UTF-8-codierte Bytes zugreifen, um die Leistung in Szenarien zu verbessern, in denen Sie bereits mit UTF-8-Daten arbeiten.

Auflistungen

Zusätzliche TryAdd und TryGetValue Überladungen für OrderedDictionary<TKey, TValue>

OrderedDictionary<TKey,TValue> bietet TryAdd und TryGetValue wie jede andere IDictionary<TKey, TValue>-Implementierung zum Hinzufügen und Abrufen. Es gibt jedoch Szenarien, in denen Sie möglicherweise weitere Vorgänge ausführen möchten, sodass neue Überladungen hinzugefügt werden, die einen Index an den Eintrag zurückgeben:

Dieser Index kann dann mit GetAt und SetAt für schnellen Zugriff auf den Eintrag verwendet werden. Ein Beispiel für die Verwendung der neuen TryAdd-Überladung ist das Hinzufügen oder Aktualisieren eines Schlüssel-Wert-Paares im geordneten Wörterbuch:

// Try to add a new key with value 1.
if (!orderedDictionary.TryAdd(key, 1, out int index))
{
    // Key was present, so increment the existing value instead.
    int value = orderedDictionary.GetAt(index).Value;
    orderedDictionary.SetAt(index, value + 1);
}

Diese neue API wird bereits in JsonObject verwendet und verbessert die Leistung der Aktualisierung von Eigenschaften um 10-20%.

Serialisierung

Die Möglichkeit bieten, ReferenceHandler in JsonSourceGenerationOptions zu spezifizieren

Wenn Sie Quellgeneratoren für die JSON-Serialisierung verwenden, wird der generierte Kontext ausgelöst, wenn Zyklen serialisiert oder deserialisiert werden. Jetzt können Sie dieses Verhalten anpassen, indem Sie das ReferenceHandler in der JsonSourceGenerationOptionsAttributeDatei angeben. Hier ist ein Beispiel für die Verwendung von JsonKnownReferenceHandler.Preserve:

public static void MakeSelfRef()
{
    SelfReference selfRef = new SelfReference();
    selfRef.Me = selfRef;

    Console.WriteLine(JsonSerializer.Serialize(selfRef, ContextWithPreserveReference.Default.SelfReference));
    // Output: {"$id":"1","Me":{"$ref":"1"}}
}

[JsonSourceGenerationOptions(ReferenceHandler = JsonKnownReferenceHandler.Preserve)]
[JsonSerializable(typeof(SelfReference))]
internal partial class ContextWithPreserveReference : JsonSerializerContext
{
}

internal class SelfReference
{
    public SelfReference Me { get; set; } = null!;
}

Option zum Verbieten doppelter JSON-Eigenschaften

Die JSON-Spezifikation gibt nicht an, wie doppelte Eigenschaften behandelt werden, wenn eine JSON-Nutzlast deserialisiert wird. Dies kann zu unerwarteten Ergebnissen und Sicherheitsrisiken führen. .NET 10 führt die JsonSerializerOptions.AllowDuplicateProperties Option ein, doppelte JSON-Eigenschaften zu verbieten:

string json = """{ "Value": 1, "Value": -1 }""";
Console.WriteLine(JsonSerializer.Deserialize<MyRecord>(json).Value); // -1

JsonSerializerOptions options = new() { AllowDuplicateProperties = false };
JsonSerializer.Deserialize<MyRecord>(json, options);                // throws JsonException
JsonSerializer.Deserialize<JsonObject>(json, options);              // throws JsonException
JsonSerializer.Deserialize<Dictionary<string, int>>(json, options); // throws JsonException

JsonDocumentOptions docOptions = new() { AllowDuplicateProperties = false };
JsonDocument.Parse(json, docOptions);   // throws JsonException

record MyRecord(int Value);

Duplikate werden erkannt, indem geprüft wird, ob ein Wert bei der Deserialisierung mehrfach zugewiesen wurde. Dies funktioniert wie erwartet mit anderen Optionen wie Groß-/Kleinschreibung und Namensrichtlinien.

Strenge JSON-Serialisierungsoptionen

Der JSON-Serializer akzeptiert viele Optionen zum Anpassen der Serialisierung und Deserialisierung, aber die Standardwerte sind für einige Anwendungen möglicherweise zu entspannt. .NET 10 fügt eine neue JsonSerializerOptions.Strict Voreinstellung hinzu, die den bewährten Methoden folgt, indem die folgenden Optionen eingeschlossen werden:

Diese Optionen sind lesekompatibel mit JsonSerializerOptions.Default - ein Objekt, das mit JsonSerializerOptions.Default serialisiert wurde, kann mit JsonSerializerOptions.Strict deserialisiert werden.

Weitere Informationen zur JSON-Serialisierung finden Sie in der Übersicht über System.Text.Json.

PipeReader-Unterstützung für JSON-Serializer

JsonSerializer.Deserialize unterstützt PipeReaderjetzt , ergänzt die bestehende PipeWriter Unterstützung. Zuvor entfällt die Deserialisierung von einer PipeReader erforderlichen Konvertierung in eine Stream, aber die neuen Überladungen beseitigen diesen Schritt, indem sie direkt in den Serialisierer integriert PipeReader werden. Als Bonus kann das, was Sie bereits halten, nicht von dem zu konvertieren, was Sie bereits halten, einige Effizienzvorteile bringen.

Dies zeigt die grundlegende Verwendung:

using System;
using System.IO.Pipelines;
using System.Text.Json;
using System.Threading.Tasks;

var pipe = new Pipe();

// Serialize to writer
await JsonSerializer.SerializeAsync(pipe.Writer, new Person("Alice"));
await pipe.Writer.CompleteAsync();

// Deserialize from reader
var result = await JsonSerializer.DeserializeAsync<Person>(pipe.Reader);
await pipe.Reader.CompleteAsync();

Console.WriteLine($"Your name is {result.Name}.");
// Output: Your name is Alice.

record Person(string Name);

Nachfolgend sehen Sie ein Beispiel für einen Produzenten, der Token in Blöcken und einem Verbraucher erzeugt, der sie empfängt und anzeigt:

using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Text.Json;
using System.Threading.Tasks;

var pipe = new Pipe();

// Producer writes to the pipe in chunks.
var producerTask = Task.Run(async () =>
{
    async static IAsyncEnumerable<Chunk> GenerateResponse()
    {
        yield return new Chunk("The quick brown fox", DateTime.Now);
        await Task.Delay(500);
        yield return new Chunk(" jumps over", DateTime.Now);
        await Task.Delay(500);
        yield return new Chunk(" the lazy dog.", DateTime.Now);
    }

    await JsonSerializer.SerializeAsync<IAsyncEnumerable<Chunk>>(pipe.Writer, GenerateResponse());
    await pipe.Writer.CompleteAsync();
});

// Consumer reads from the pipe and outputs to console.
var consumerTask = Task.Run(async () =>
{
    var thinkingString = "...";
    var clearThinkingString = new string("\b\b\b");
    var lastTimestamp = DateTime.MinValue;

    // Read response to end.
    Console.Write(thinkingString);
    await foreach (var chunk in JsonSerializer.DeserializeAsyncEnumerable<Chunk>(pipe.Reader))
    {
        Console.Write(clearThinkingString);
        Console.Write(chunk.Message);
        Console.Write(thinkingString);
        lastTimestamp = DateTime.Now;
    }

    Console.Write(clearThinkingString);
    Console.WriteLine($" Last message sent at {lastTimestamp}.");

    await pipe.Reader.CompleteAsync();
});

await producerTask;
await consumerTask;

record Chunk(string Message, DateTime Timestamp);

All dies wird als JSON in der Pipe (hier zur Lesbarkeit formatiert) serialisiert:

[
    {
        "Message": "The quick brown fox",
        "Timestamp": "2025-08-01T18:37:27.2930151-07:00"
    },
    {
        "Message": " jumps over",
        "Timestamp": "2025-08-01T18:37:27.8594502-07:00"
    },
    {
        "Message": " the lazy dog.",
        "Timestamp": "2025-08-01T18:37:28.3753669-07:00"
    }
]

System.Numerik

Weitere linkshändige Matrixtransformationsmethoden

.NET 10 fügt die verbleibenden APIs zum Erstellen von linkshändigen Transformationsmatrizen für Billboard- und Constrained-Billboard-Matrizen hinzu. Sie können diese Methoden wie ihre vorhandenen rechtshändigen Gegenstücke verwenden, z. B. CreateBillboard(Vector3, Vector3, Vector3, Vector3), wenn Sie stattdessen ein linkshändiges Koordinatensystem verwenden:

Tensor-Verbesserungen

Der System.Numerics.Tensors-Namespace enthält nun eine nichtgenerische Schnittstelle, IReadOnlyTensor, für Vorgänge wie den Zugriff auf Lengths und Strides. Datenschnitte kopieren keine Daten mehr, wodurch die Leistung verbessert wird. Darüber hinaus können Sie nicht-generisch auf Daten zugreifen, indem Sie Boxing, bis object die Leistung nicht kritisch ist.

Die Tensor-APIs sind jetzt stabil und nicht mehr als experimentell gekennzeichnet. Während die APIs weiterhin auf das NuGet-Paket "System.Numerics.Tensors " verweisen müssen, wurden sie für die .NET 10-Version gründlich überprüft und abgeschlossen. Die Typen nutzen C# 14-Erweiterungsoperatoren, um arithmetische Vorgänge bereitzustellen, wenn der zugrunde liegende Typ T den Vorgang unterstützt. Wenn T die relevanten generischen mathematischen Schnittstellen implementiert, z.B. IAdditionOperators<TSelf, TOther, TResult> oder INumber<TSelf>, wird der Vorgang unterstützt. Beispielsweise ist tensor + tensor für ein Tensor<int> verfügbar, aber nicht für ein Tensor<bool>.

Optionsüberprüfung

Neuer AOT-sicherer Konstruktor für ValidationContext

Die ValidationContext Klasse, die während der Überprüfung der Optionen verwendet wird, enthält eine neue Konstruktorüberladung, die den displayName Parameter explizit akzeptiert:

ValidationContext(Object, String, IServiceProvider, IDictionary<Object,Object>)

Die AOT-Sicherheit wird durch den Anzeigenamen gewährleistet, wodurch seine Verwendung in nativen Builds ohne Warnungen ermöglicht wird.

Diagnostik

Unterstützung für Telemetrieschema-URLs in ActivitySource und Meter

ActivitySource und Meter unterstützen jetzt die Angabe einer Telemetrieschema-URL während der Konstruktion, die mit OpenTelemetry-Spezifikationen übereinstimmt. Das Telemetrieschema stellt Konsistenz und Kompatibilität für Tracing- und Metrikdaten sicher. Darüber hinaus führt .NET 10 ein ActivitySourceOptions, das die Erstellung von ActivitySource Instanzen mit mehreren Konfigurationsoptionen (einschließlich der Telemetrieschema-URL) vereinfacht.

Die neuen APIs sind:

Die Activity Klasse ermöglicht die verteilte Ablaufverfolgung, indem der Ablauf von Vorgängen über Dienste oder Komponenten hinweg nachverfolgt wird. .NET unterstützt die Serialisierung dieser Ablaufverfolgungsdaten über den Microsoft-Diagnostics-DiagnosticSource Ereignisquellenanbieter. Ein Activity kann zusätzliche Metadaten wie ActivityLink und ActivityEvent enthalten. .NET 10 bietet Unterstützung für die Serialisierung dieser Verknüpfungen und Ereignisse, sodass out-of-proc-Ablaufverfolgungsdaten jetzt diese Informationen enthalten. Beispiel:

Events->"[(TestEvent1,​2025-03-27T23:34:10.6225721+00:00,​[E11:​EV1,​E12:​EV2]),​(TestEvent2,​2025-03-27T23:34:11.6276895+00:00,​[E21:​EV21,​E22:​EV22])]"
Links->"[(19b6e8ea216cb2ba36dd5d957e126d9f,​98f7abcb3418f217,​Recorded,​null,​false,​[alk1:​alv1,​alk2:​alv2]),​(2d409549aadfdbdf5d1892584a5f2ab2,​4f3526086a350f50,​None,​null,​false)]"

Unterstützung für die Begrenzung der Ablaufverfolgungs-Samplingrate

Wenn verteilte Ablaufverfolgungsdaten über den Microsoft-Diagnostics-DiagnosticSource Ereignisquellenanbieter serialisiert werden, können entweder alle aufgezeichneten Aktivitäten ausgegeben werden, oder es kann ein Sampling basierend auf einem Ablaufverfolgungs-Verhältnis angewendet werden.

Eine neue Samplingoption namens "Rate Limiting Sampling" schränkt die Anzahl der pro Sekunde serialisierten Stammaktivitäten ein. Dadurch können Sie die Datenlautstärke genauer steuern.

Prozessexterne Ablaufverfolgungs-Daten-Aggregatoren können dieses Sampling aktivieren und konfigurieren, indem sie die Option in FilterAndPayloadSpecs angeben. Die folgende Einstellung beschränkt beispielsweise die Serialisierung auf 100 Stammaktivitäten pro Sekunde für alle ActivitySource Instanzen:

[AS]*/-ParentRateLimitingSampler(100)

ZIP-Dateien

Verbesserungen der ZipArchive-Leistung und des Arbeitsspeichers

.NET 10 verbessert die Leistung und Speicherauslastung von ZipArchive.

Zunächst wurde die Art und Weise optimiert, wie Einträge in eine ZipArchive geschrieben werden, wenn der Update-Modus aktiv ist. Zuvor wurden alle ZipArchiveEntry Instanzen in den Arbeitsspeicher geladen und neu geschrieben, was zu hohen Speicherauslastungs- und Leistungsengpässen führen könnte. Durch die Optimierung wird die Speicherauslastung reduziert und die Leistung verbessert, da nicht alle Einträge in den Arbeitsspeicher geladen werden müssen.

Zweitens wird die Extraktion von ZipArchive Einträgen parallelisiert, und interne Datenstrukturen sind für eine bessere Speicherauslastung optimiert. Diese Verbesserungen behandeln Probleme im Zusammenhang mit Leistungsengpässen und einer hohen Speicherauslastung, wodurch ZipArchive effizienter und schneller werden, insbesondere beim Umgang mit großen Archiven.

Neue asynchrone ZIP-APIs

.NET 10 führt neue asynchrone APIs ein, die das Ausführen von nicht blockierenden Vorgängen beim Lesen oder Schreiben in ZIP-Dateien vereinfachen. Dieses Feature wurde von der Community sehr angefordert.

Neue async Methoden stehen zum Extrahieren, Erstellen und Aktualisieren von ZIP-Archiven zur Verfügung. Mit diesen Methoden können Entwickler große Dateien effizient verarbeiten und die Reaktionsfähigkeit der Anwendung verbessern, insbesondere in Szenarien mit I/O-gebundenen Vorgängen. Zu diesen Methoden gehören:

Beispiele für die Verwendung dieser APIs finden Sie im Blogbeitrag "Vorschau 4".

Leistungsverbesserung in GZipStream für verkettete Datenströme

Ein Communitybeitrag verbesserte die Leistung von GZipStream bei der Verarbeitung verketteter GZip-Datenströme. Zuvor gab jedes neue Datenstromsegment den internen ZLibStreamHandle frei und wies ihn neu zu, was zu zusätzlichen Speicherzuweisungen und einem erhöhten Initialisierungsaufwand führte. Mit dieser Änderung wird das Handle jetzt zurückgesetzt und wiederverwendet, um verwaltete und nicht verwaltete Speicherzuweisungen zu reduzieren und die Ausführungszeit zu verbessern. Der größte Leistungszuwachs (~35 % schneller) zeigt sich bei der Verarbeitung einer großen Anzahl kleiner Datenströme. Diese Änderung:

  • Eliminiert wiederholtes Zuweisen von ca. 64-80 Bytes Arbeitsspeicher pro verketteten Datenstrom, mit zusätzlichen Einsparungen bei nicht verwaltetem Speicher.
  • Verringert die Ausführungszeit um ca. 400 ns pro verketteten Datenstrom.

Windows-Prozessverwaltung

Starten von Windows-Prozessen in einer neuen Prozessgruppe

Für Windows können Sie jetzt ProcessStartInfo.CreateNewProcessGroup einen Prozess in einer separaten Prozessgruppe starten. Auf diese Weise können Sie isolierte Signale an untergeordnete Prozesse senden, die andernfalls das übergeordnete Element ohne ordnungsgemäße Behandlung herunternehmen könnten. Das Senden von Signalen ist praktisch, um eine erzwungene Beendigung zu vermeiden.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        bool isChildProcess = args.Length > 0 && args[0] == "child";
        if (!isChildProcess)
        {
            var psi = new ProcessStartInfo
            {
                FileName = Environment.ProcessPath,
                Arguments = "child",
                CreateNewProcessGroup = true,
            };

            using Process process = Process.Start(psi)!;
            Thread.Sleep(5_000);

            GenerateConsoleCtrlEvent(CTRL_C_EVENT, (uint)process.Id);
            process.WaitForExit();

            Console.WriteLine("Child process terminated gracefully, continue with the parent process logic if needed.");
        }
        else
        {
            // If you need to send a CTRL+C, the child process needs to re-enable CTRL+C handling, if you own the code, you can call SetConsoleCtrlHandler(NULL, FALSE).
            // see https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#remarks
            SetConsoleCtrlHandler((IntPtr)null, false);

            Console.WriteLine("Greetings from the child process!  I need to be gracefully terminated, send me a signal!");

            bool stop = false;

            var registration = PosixSignalRegistration.Create(PosixSignal.SIGINT, ctx =>
            {
                stop = true;
                ctx.Cancel = true;
                Console.WriteLine("Received CTRL+C, stopping...");
            });

            StreamWriter sw = File.AppendText("log.txt");
            int i = 0;
            while (!stop)
            {
                Thread.Sleep(1000);
                sw.WriteLine($"{++i}");
                Console.WriteLine($"Logging {i}...");
            }

            // Clean up
            sw.Dispose();
            registration.Dispose();

            Console.WriteLine("Thanks for not killing me!");
        }
    }

    private const int CTRL_C_EVENT = 0;
    private const int CTRL_BREAK_EVENT = 1;

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetConsoleCtrlHandler(IntPtr handler, [MarshalAs(UnmanagedType.Bool)] bool Add);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
}

WebSocket-Verbesserungen

WebSocketStream

.NET 10 führt WebSocketStreameine neue API ein, die dazu dient, einige der am häufigsten verwendeten – und zuvor umständlichen –WebSocket Szenarien in .NET zu vereinfachen.

Herkömmliche WebSocket APIs sind auf niedriger Ebene und erfordern einen wichtigen Textbaustein: Behandeln von Puffern und Rahmen, Rekonstruieren von Nachrichten, Verwalten von Codierung/Decodierung und Schreiben von benutzerdefinierten Wrappern zur Integration in Datenströme, Kanäle oder andere Transportabstraktionen. Diese Komplexitäten erschweren die Verwendung von WebSockets als Transport, insbesondere für Apps mit Streaming- oder textbasierten Protokollen oder ereignisgesteuerten Handlern.

WebSocketStream behebt diese Schmerzpunkte, indem eine Stream-basierte Abstraktion über ein WebSocket bereitgestellt wird. Dies ermöglicht eine nahtlose Integration in vorhandene APIs zum Lesen, Schreiben und Analysieren von Daten, ob binär oder Text, und reduziert den Bedarf an manueller Sanitärinstallation.

WebSocketStream ermöglicht allgemeine, vertraute APIs für allgemeine WebSocket-Verbrauchs- und Produktionsmuster. Diese APIs reduzieren die Reibung und vereinfachen die Implementierung erweiterter Szenarien.

Allgemeine Verwendungsmuster

Hier sind einige Beispiele WebSocketStream für die Vereinfachung typischer WebSocket Workflows:

Streamingtextprotokoll (z. B. STOMP)
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

// Streaming text protocol (for example, STOMP).
using Stream transportStream = WebSocketStream.Create(
    connectedWebSocket, 
    WebSocketMessageType.Text,
    ownsWebSocket: true);
// Integration with Stream-based APIs.
// Don't close the stream, as it's also used for writing.
using var transportReader = new StreamReader(transportStream, leaveOpen: true); 
var line = await transportReader.ReadLineAsync(cancellationToken); // Automatic UTF-8 and new line handling.
transportStream.Dispose(); // Automatic closing handshake handling on `Dispose`.
Binäres Streamingprotokoll (z. B. AMQP)
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

// Streaming binary protocol (for example, AMQP).
Stream transportStream = WebSocketStream.Create(
    connectedWebSocket,
    WebSocketMessageType.Binary,
    closeTimeout: TimeSpan.FromSeconds(10));
await message.SerializeToStreamAsync(transportStream, cancellationToken);
var receivePayload = new byte[payloadLength];
await transportStream.ReadExactlyAsync(receivePayload, cancellationToken);
transportStream.Dispose();
// `Dispose` automatically handles closing handshake.
Lesen einer einzelnen Nachricht als Datenstrom (z. B. JSON-Deserialisierung)
using System.IO;
using System.Net.WebSockets;
using System.Text.Json;

// Reading a single message as a stream (for example, JSON deserialization).
using Stream messageStream = WebSocketStream.CreateReadableMessageStream(connectedWebSocket, WebSocketMessageType.Text);
// JsonSerializer.DeserializeAsync reads until the end of stream.
var appMessage = await JsonSerializer.DeserializeAsync<AppMessage>(messageStream);
Schreiben einer einzelnen Nachricht als Datenstrom (z. B. binäre Serialisierung)
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

// Writing a single message as a stream (for example, binary serialization).
public async Task SendMessageAsync(AppMessage message, CancellationToken cancellationToken)
{
    using Stream messageStream = WebSocketStream.CreateWritableMessageStream(_connectedWebSocket, WebSocketMessageType.Binary);
    foreach (ReadOnlyMemory<byte> chunk in message.SplitToChunks())
    {
        await messageStream.WriteAsync(chunk, cancellationToken);
    }
} // EOM sent on messageStream.Dispose().

TLS-Verbesserungen

TLS 1.3 für macOS (Client)

.NET 10 fügt clientseitige TLS 1.3-Unterstützung für macOS hinzu, indem apples Network.framework in SslStream und HttpClientintegriert wird. In der Vergangenheit verwendete macOS Secure Transport, der TLS 1.3 nicht unterstützt; Das Anmelden bei Network.framework ermöglicht TLS 1.3.

Bereich und Verhalten

  • nur macOS, clientseitig in dieser Version.
  • Opt-In. Vorhandene Apps verwenden weiterhin den aktuellen Stapel, es sei denn, sie sind aktiviert.
  • Wenn diese Option aktiviert ist, sind ältere TLS-Versionen (TLS 1.0 und 1.1) möglicherweise nicht mehr über Network.framework verfügbar.

Vorgehensweise zum Aktivieren

Verwenden Sie einen AppContext-Switch im Code:

// Opt in to Network.framework-backed TLS on Apple platforms.
AppContext.SetSwitch("System.Net.Security.UseNetworkFramework", true);

using var client = new HttpClient();
var html = await client.GetStringAsync("https://example.com");

Oder verwenden Sie eine Umgebungsvariable:

# Opt-in via environment variable (set for the process or machine as appropriate)
DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK=1
# or
DOTNET_SYSTEM_NET_SECURITY_USENETWORKFRAMEWORK=true

Hinweise

  • TLS 1.3 gilt für SslStream darauf basierende APIs (z. B HttpClient/HttpMessageHandler. ).
  • Cipher Suites werden über Network.framework von macOS gesteuert.
  • Das zugrunde liegende Datenstromverhalten kann sich unterscheiden, wenn Network.framework aktiviert ist (z. B. Pufferung, Lese-/Schreibzugriff, Abbruchsemantik).
  • Die Semantik kann sich bei Lesevorgängen von Nullbyte unterscheiden. Verlassen Sie sich nicht auf Lesevorgänge der Länge Null, um die Datenverfügbarkeit zu erkennen.
  • Bestimmte internationalisierte Domänennamen (IDN)-Hostnamen werden möglicherweise von Network.framework abgelehnt. Bevorzugen Sie ASCII-/Punycode-Hostnamen (A-Label) oder überprüfen Sie Namen anhand von MacOS/Network.framework-Einschränkungen.
  • Wenn Ihre App auf bestimmtes SslStream Edge-Case-Verhalten angewiesen ist, überprüfen Sie sie unter Network.framework.