Novinky v knihovnách .NET pro .NET 9

Tento článek popisuje nové funkce v knihovnách .NET pro .NET 9.

Base64Url

Base64 je schéma kódování, které překládá libovolné bajty na text složený z konkrétní sady 64 znaků. Je to běžný přístup k přenosu dat a byl již dlouho podporován prostřednictvím různých metod, jako je například s Convert.ToBase64String nebo Base64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Některé znaky, které používá, jsou ale v některých případech méně než ideální pro použití, které byste jinak mohli chtít použít, například v řetězcích dotazu. Konkrétně 64 znaků, které tvoří tabulku Base64, obsahují "+" a "/", z nichž oba mají svůj vlastní význam v adresách URL. To vedlo k vytvoření schématu Base64Url, který je podobný Base64, ale používá mírně odlišnou sadu znaků, která je vhodná pro použití v kontextech adres URL. .NET 9 obsahuje novou třídu Base64Url, která poskytuje mnoho užitečných a optimalizovaných metod pro kódování a dekódování pomocí Base64Url do a z různých datových typů.

Následující příklad ukazuje použití nové třídy.

ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);

BinaryFormatter (Binární formátovač)

.NET 9 odebere BinaryFormatter z modulu runtime .NET. Rozhraní API jsou stále dostupná, ale jejich implementace vždy vyvolá výjimku bez ohledu na typ projektu. Další informace o odebrání a možnostech, pokud se vás to týká, najdete v průvodci migrací BinaryFormatter.

Sbírky

Typy kolekcí v .NET získávají následující aktualizace pro .NET 9:

Vyhledávání kolekcí s rozsahy

Ve vysoce výkonném kódu se často používají rozsahy, aby se zbytečně nepřidělovaly řetězce, a vyhledávací tabulky s typy, jako jsou Dictionary<TKey,TValue> a HashSet<T>, se často používají jako mezipaměti. Neexistuje však žádný bezpečný integrovaný mechanismus pro vyhledávání těchto typů kolekcí s úseky. S novou funkcí allows ref struct v jazyce C# 13 a novými funkcemi těchto typů kolekcí v .NET 9 je teď možné tyto druhy vyhledávání provádět.

Následující příklad ukazuje použití 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>

V mnoha scénářích můžete chtít páry klíč-hodnota uložit způsobem, ve kterém je možné udržovat pořadí (seznam párů klíč-hodnota), ale kde je také podporován rychlý vyhledávání podle klíče (slovník párů klíč-hodnota). Od prvních dnů .NET podporuje tento scénář typ OrderedDictionary, ale pouze ne generickým způsobem s klíči a hodnotami zadanými jako object. .NET 9 představuje dlouho požadovanou kolekci OrderedDictionary<TKey,TValue>, která poskytuje efektivní obecný typ pro podporu těchto scénářů.

Následující kód používá novou třídu.

OrderedDictionary<string, int> d = new()
{
    ["a"] = 1,
    ["b"] = 2,
    ["c"] = 3,
};

d.Add("d", 4);
d.RemoveAt(0);
d.RemoveAt(2);
d.Insert(0, "e", 5);

foreach (KeyValuePair<string, int> entry in d)
{
    Console.WriteLine(entry);
}

// Output:
// [e, 5]
// [b, 2]
// [c, 3]

Metoda PriorityQueue.Remove()

.NET 6 zavedl kolekci PriorityQueue<TElement,TPriority>, která poskytuje jednoduchou a rychlou implementaci pole ve formě haldy. Jedním z obecných problémů s haldami polí je, že nepodporují aktualizace priority, což z nich znemožňuje použití v algoritmech, jako jsou varianty algoritmu Dijkstra.

I když v existující kolekci není možné implementovat efektivní aktualizace priority $O(\log n)$, nová PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) metoda umožňuje emulovat aktualizace priority (i když v době $O(n)$):

public static void UpdatePriority<TElement, TPriority>(
    this PriorityQueue<TElement, TPriority> queue,
    TElement element,
    TPriority priority
    )
{
    // Scan the heap for entries matching the current element.
    queue.Remove(element, out _, out _);
    // Re-insert the entry with the new priority.
    queue.Enqueue(element, priority);
}

Tato metoda odblokuje uživatele, kteří chtějí implementovat algoritmy grafu v kontextech, kde asymptotický výkon není blokátorem. (Tyto kontexty zahrnují vzdělávání a vytváření prototypů.) Tady je například ukázková verze algoritmu Dijkstra, která používá nové API.

ReadOnlySet<T>

Často je žádoucí poskytnout kolekce ve zobrazení pouze pro čtení. ReadOnlyCollection<T> umožňuje vytvořit obálku jen pro čtení kolem libovolné proměnlivé IList<T>, a ReadOnlyDictionary<TKey,TValue> umožňuje vytvořit obálku jen pro čtení kolem libovolné proměnlivé IDictionary<TKey,TValue>. Předchozí verze .NET ale neměly žádnou integrovanou podporu pro stejný postup s ISet<T>. .NET 9 zavádí ReadOnlySet<T>, aby se to vyřešilo.

Nová třída umožňuje následující vzor použití.

private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;

public ReadOnlySet<int> Set => _setWrapper ??= new(_set);

Model komponent – TypeDescriptor podpora oříznutí

System.ComponentModel obsahuje nová rozhraní API s možností opt-in, která jsou kompatibilní s trimmerem, pro popis komponent. Jakákoli aplikace, zvláště samostatné zredukované aplikace, může využívat tato nová rozhraní API k podpoře scénářů zredukování.

Primárním rozhraním API je TypeDescriptor.RegisterType metoda ve TypeDescriptor třídě. Tato metoda má atribut DynamicallyAccessedMembersAttribute, aby trimr zachoval členy pro tento typ. Tuto metodu byste měli volat jednou pro každý typ, a to obvykle na začátku.

Sekundární rozhraní API mají příponu FromRegisteredType , například TypeDescriptor.GetPropertiesFromRegisteredType(Type). Na rozdíl od jejich protějšků, které nemají příponu FromRegisteredType, tato rozhraní API nemají ani atributy [RequiresUnreferencedCode] ani atributy trimmeru [DynamicallyAccessedMembers]. Nedostatek atributů vlasového zastřihovače pomáhá spotřebitelům tím, že už nemusí dělat následující:

  • Zakázat varování o oříznutí, což může být rizikové.
  • Přenést silně typovaný parametr Type do jiných metod, což může být těžkopádné nebo neproveditelné.
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; }
}

Další informace najdete v návrhu rozhraní API .

Kryptografie

Metoda CryptographicOperations.HashData()

.NET zahrnuje několik statických "one-shot" implementací hashovacích funkcí a souvisejících funkcí. Tato rozhraní API zahrnují SHA256.HashData a HMACSHA256.HashData. Jednorázová API jsou preferována k použití, protože poskytují nejlepší možný výkon a snižují nebo eliminují přidělení.

Pokud vývojář chce poskytnout rozhraní API, které podporuje hashování, ve kterém volající definuje, který hashovací algoritmus se má použít, obvykle se provádí přijetím argumentu HashAlgorithmName . Použití takového vzoru s jednorázovými API by však vyžadovalo přepnutí všechny možné HashAlgorithmName a následné použití vhodné metody. Pokud chcete tento problém vyřešit, .NET 9 zavádí rozhraní API CryptographicOperations.HashData. Toto rozhraní API umožňuje vytvořit hodnotu hash nebo HMAC nad vstupem jako jednorázovou operaci, kde použitý algoritmus je určen hodnotou HashAlgorithmName.

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

Algoritmus KMAC

.NET 9 poskytuje algoritmus KMAC určený NIST SP-800-185. KECCAK Message Authentication Code (KMAC) je pseudonáhodná funkce a klíčovaná hash funkce založená na KECCAK.

Následující nové třídy používají algoritmus KMAC. Pomocí instancí můžete shromáždit data pro vytvoření MAC, nebo použít statickou HashData metodu pro jednorázové zpracování jednoho vstupu.

KMAC je k dispozici v Linuxu s OpenSSL 3.0 nebo novějším a v Windows 11 Buildu 26016 nebo novějším. Statickou IsSupported vlastnost můžete použít k určení, jestli platforma podporuje požadovaný algoritmus.

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.
}

algoritmy AES-GCM a ChaChaPoly1305 povolené pro iOS/tvOS/MacCatalyst

IsSupported a ChaChaPoly1305.IsSupported nyní vracejí "true" při spuštění na iOS 13+, tvOS 13+ a Mac Catalyst.

AesGcm Podporuje pouze 16bajtů (128bitové) hodnoty značek v operačních systémech Apple.

Načítání certifikátu X.509

Od .NET Framework 2.0 je způsob načtení certifikátu new X509Certificate2(bytes). Byly zde také další vzory, například new X509Certificate2(bytes, password, flags), new X509Certificate2(path), new X509Certificate2(path, password, flags)a X509Certificate2Collection.Import(bytes, password, flags) (a jeho přetížení).

Všechny tyto metody použily obsahové snímání k zjištění, jestli vstup byl něco, co dokázaly zpracovat, a pak ho načetly, pokud to bylo možné. U některých volajících byla tato strategie velmi pohodlná. Má ale také několik problémů:

  • Ne každý formát souboru funguje v každém operačním systému.
  • Jedná se o odchylku od protokolu.
  • Jde o zdroj problémů se zabezpečením.

.NET 9 zavádí novou třídu X509CertificateLoader, která má návrh "jedné metody, jeden účel". Ve své počáteční verzi podporuje pouze dva z pěti formátů, které X509Certificate2 konstruktor podporuje. Jedná se o dva formáty, které fungovaly na všech operačních systémech.

Podpora poskytovatelů OpenSSL

.NET 8 zavedla rozhraní API specifická pro OpenSSL OpenPrivateKeyFromEngine(String, String) a OpenPublicKeyFromEngine(String, String). Umožňují interakci s komponentami OpenSSL ENGINE a používají například moduly hardwarového zabezpečení (HSM).

.NET 9 zavádí SafeEvpPKeyHandle.OpenKeyFromProvider(String, String), která umožňuje používat poskytovatele OpenSSL a interakci s poskytovateli, jako jsou tpm2 nebo pkcs11.

Některá distribuce odebrala ENGINE podporu , protože už je zastaralá.

Následující fragment kódu ukazuje základní použití:

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.
}

Během handshake protokolu TLS dochází k určitým vylepšením výkonu, a také k vylepšení interakce s privátními klíči RSA používajícími komponenty ENGINE.

zabezpečení CNG Windows založené na virtualizaci

Windows 11 přidalo nová rozhraní API, která pomáhají zabezpečit klíče Windows pomocí zabezpečení založeného na virtualizaci (VBS). Díky této nové funkci je možné klíče chránit před útoky na krádež klíčů na úrovni správce se zanedbatelným vlivem na výkon, spolehlivost a škálování.

.NET 9 přidal odpovídající příznaky CngKeyCreationOptions. Byly přidány následující tři příznaky:

  • CngKeyCreationOptions.PreferVbs odpovídající NCRYPT_PREFER_VBS_FLAG
  • CngKeyCreationOptions.RequireVbs odpovídající NCRYPT_REQUIRE_VBS_FLAG
  • CngKeyCreationOptions.UsePerBootKey odpovídající NCRYPT_USE_PER_BOOT_KEY_FLAG

Následující ukázka kódu ukazuje, jak použít jednu z vlajek.

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 a čas – nové přetížení TimeSpan.From*

Třída TimeSpan nabízí několik From* metod, které umožňují vytvořit TimeSpan objekt pomocí double. Vzhledem k tomu, že double je binární formát s plovoucí desetinnou čárkou, vnitřní nepřesnost může vést k chybám. Například nemusí TimeSpan.FromSeconds(101.832) přesně představovat 101 seconds, 832 milliseconds, ale spíše přibližně 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds. Tato nesrovnalost způsobila časté nejasnosti a není to také nejúčinnější způsob, jak taková data znázornit. Chcete-li to vyřešit, .NET 9 přidá nové přetížení, které umožňují vytvořit TimeSpan objekty z celých čísel. Existují nová přetížení z FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds a FromMicroseconds.

Příklad následujícího kódu ukazuje volání double a jednu z nových celočíselných přetížení.

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

Injektování závislostí – ActivatorUtilities.CreateInstance konstruktor

Rozlišení konstruktoru pro ActivatorUtilities.CreateInstance se v .NET 9 změnilo. Dříve nelze volat konstruktor, který byl explicitně označen pomocí atributu ActivatorUtilitiesConstructorAttribute , v závislosti na pořadí konstruktorů a počtu parametrů konstruktoru. Logika se v .NET 9 změnila tak, aby se vždy volal konstruktor, který má atribut.

Diagnostika

Debug.Assert ve výchozím nastavení hlásí podmínku assert

Debug.Assert se běžně používá k ověření podmínek, u kterých se očekává, že budou vždy pravdivé. Selhání obvykle značí chybu v kódu. Existuje mnoho variant přetížení Debug.Assert, kde nejjednodušší z nich pouze přijímá podmínku.

Debug.Assert(a > 0 && b > 0);

Tvrzení selže, pokud je podmínka nepravdivá. Historicky však taková tvrzení postrádala jakékoli informace o tom, jaká podmínka selhala. Počínaje .NET 9, pokud uživatel neposkytne žádnou zprávu explicitně, bude assert obsahovat textové vyjádření podmínky. Například v předchozím příkladu s použitím příkazu „assert“ namísto zobrazení zprávy jako:

Process terminated. Assertion failed.
   at Program.SomeMethod(Int32 a, Int32 b)

Zpráva by teď byla:

Process terminated. Assertion failed.
a > 0 && b > 0
   at Program.SomeMethod(Int32 a, Int32 b)

Dříve jste mohli trasování Activity propojit pouze s jinými kontexty trasování při vytváření objektu Activity. Nové v .NET 9 umožňuje rozhraní API AddLink(ActivityLink) propojit objekt Activity s dalšími kontexty trasování po jeho vytvoření. Tato změna odpovídá také specifikacím OpenTelemetry .

ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);

Activity activity = new("LinkTest");
activity.AddLink(activityLink);

Měřicí nástroj Metrics.Gauge

System.Diagnostics.Metrics nyní poskytuje Gauge<T> nástroj podle specifikace OpenTelemetry. Nástroj Gauge je navržen tak, aby zaznamenával neaditivní hodnoty, když dojde ke změnám. Může například měřit úroveň šumu na pozadí, kdy by sčítání hodnot z více místností bylo nesmyslné. Nástroj Gauge je obecný typ, který může zaznamenávat libovolný typ hodnoty, například int, doublenebo decimal.

Následující příklad ukazuje použití Gauge nástroje.

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" } });

Naslouchání zástupného znaku měřiče v režimu out-of-proc

Pomocí poskytovatele zdroje událostí System.Diagnostics.Metrics už je možné naslouchat měřičům mimo proces, ale před .NET 9 jste museli zadat úplný název měřiče. V .NET 9 můžete poslouchat všechny měřiče pomocí zástupného znaku *, což umožňuje zachytit metriky z každého měřiče v procesu. Kromě toho přidává podporu naslouchání podle předpony měřiče, takže můžete naslouchat všem měřičům, jejichž názvy začínají zadanou předponou. Například určení MyMeter* umožňuje naslouchání všem měřičům s názvy, které začínají MyMeter.

// The complete meter name is "MyCompany.MyMeter".
var meter = new Meter("MyCompany.MyMeter");
// Create a counter and allow publishing values.
meter.CreateObservableCounter("MyCounter", () => 1);

// Create the listener to use the wildcard character
// to listen to all meters using prefix names.
MyEventListener listener = new MyEventListener();

Třída MyEventListener je definována následujícím způsobem.

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]}");
        }
    }
}

Při spuštění kódu je výstup následující:

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

Zástupný znak můžete také použít k naslouchání metrikám pomocí monitorovacích nástrojů, jako jsou dotnet-counters.

LINQ

Byly zavedeny nové metody CountByAggregateBy . Tyto metody umožňují agregovat stav podle klíče, aniž by bylo nutné přidělovat přechodné seskupení prostřednictvím GroupBy.

CountBy umožňuje rychle vypočítat frekvenci jednotlivých klíčů. Následující příklad najde slovo, které se vyskytuje nejčastěji v textovém řetězci.

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";

// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

AggregateBy umožňuje implementovat více obecných pracovních postupů. Následující příklad ukazuje, jak můžete vypočítat skóre, která jsou přidružena k danému klíči.

(string id, int score)[] data =
    [
        ("0", 42),
        ("1", 5),
        ("2", 4),
        ("1", 10),
        ("0", 25),
    ];

var aggregatedData =
    data.AggregateBy(
        keySelector: entry => entry.id,
        seed: 0,
        (totalScore, curr) => totalScore + curr.score
        );

foreach (var item in aggregatedData)
{
    Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)

Index<TSource>(IEnumerable<TSource>) umožňuje rychle extrahovat implicitní index enumerovatelné. Teď můžete napsat kód, například následující fragment kódu, který automaticky indexuje položky v kolekci.

IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

Generátor zdroje protokolování

C# 12 zavedl primární konstruktory, které umožňují definovat konstruktor přímo na deklaraci třídy. Generátor zdroje protokolování teď podporuje protokolování pomocí tříd, které mají primární konstruktor.

public partial class ClassWithPrimaryConstructor(ILogger logger)
{
    [LoggerMessage(0, LogLevel.Debug, "Test.")]
    public partial void Test();
}

Různé

V této části najdete informace o:

allows ref struct používané v knihovnách

C# 13 zavádí možnost omezit obecný parametr s allows ref struct, který říká kompilátoru a modulu runtime, že ref struct lze použít pro tento obecný parametr. Mnoho rozhraní API, která jsou s tímto rozhraním kompatibilní, byla nyní opatřena poznámkami. Například metoda String.Create má přetížení, které umožňuje vytvořit string pomocí zápisu přímo do její paměti, která je reprezentována jako úsek. Tato metoda má TState argument, který je předán ze strany volajícího do delegáta, který provádí skutečné psaní.

Tento TState parametr typu na String.Create je nyní opatřen poznámkami allows ref struct:

public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
    where TState : allows ref struct;

Tato poznámka umožňuje předat rozsah (nebo jakýkoli jiný ref struct) jako vstup pro tuto metodu.

Následující příklad ukazuje nové String.ToLowerInvariant() přetížení, které využívá tuto novou funkci.

public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
    string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));

SearchValues expanze

.NET 8 zavedl typ SearchValues<T>, který poskytuje optimalizované řešení pro vyhledávání konkrétních sad znaků nebo bajtů v rámci rozsahů. V .NET 9 byla SearchValues rozšířena tak, aby podporovala hledání podřetězců ve větším řetězci.

Následující příklad vyhledá více názvů zvířat v řetězcové hodnotě a vrátí index prvního nalezeného.

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);

Tato nová funkce má optimalizovanou implementaci, která využívá podporu SIMD v podkladové platformě. Umožňuje také optimalizaci typů vyšší úrovně. Tuto funkci teď například Regex využívá jako součást své implementace.

Sítě

SocketsHttpHandler je ve výchozím nastavení v HttpClientFactory

HttpClientFactory vytváří HttpClient objekty zálohované HttpClientHandlerve výchozím nastavení. HttpClientHandler je samo o sobě podporováno SocketsHttpHandler, což je mnohem lépe konfigurovatelné, včetně správy životnosti připojení. HttpClientFactory nyní ve výchozím nastavení používá SocketsHttpHandler a konfiguruje jej tak, aby nastavil limity životnosti připojení, které odpovídají životnosti rotace specifikované ve frameworku.

System.Net.ServerSentEvents

Události odeslané serverem (SSE) jsou jednoduchým a oblíbeným protokolem pro streamování dat ze serveru do klienta. Používá ho například OpenAI jako součást streamování vygenerovaného textu ze svých služeb AI. Pro zjednodušení spotřeby SSE poskytuje nová System.Net.ServerSentEvents knihovna analyzátor pro snadné ingestování událostí odeslaných serverem.

Následující kód ukazuje použití nové třídy.

Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
    Console.WriteLine(e.Data);
}

Obnovení protokolu TLS s klientskými certifikáty v Linuxu

Obnovení protokolu TLS je funkce protokolu TLS, která umožňuje obnovení dříve vytvořených relací na server. Tím se vyhnete několika výměnám přes síť a během protokolu TLS handshake šetříte výpočetní prostředky.

Obnovení protokolu TLS již bylo podporováno v Linuxu pro připojení SslStream bez klientských certifikátů. .NET 9 přidává podporu obnovení protokolu TLS při vzájemně ověřených připojeních TLS, což jsou běžné ve scénářích mezi servery. Funkce je povolená automaticky.

Příkaz ping a vypršení časového limitu protokolu WebSocket keep-alive

Nová rozhraní API na ClientWebSocketOptions a WebSocketCreationOptions vám umožňují aktivovat odesílání pingů WebSocket a ukončení připojení, pokud protistrana nereaguje včas.

Až dosud jste mohli zadat KeepAliveInterval k tomu, aby připojení nezůstalo nečinné, ale neexistoval žádný vestavěný mechanismus, který by vynucoval, že protějšek reaguje.

Následující příklad odešle příkaz ping na server každých 5 sekund a přeruší připojení, pokud nereaguje během sekundy.

using var cws = new ClientWebSocket();
cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
cws.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
cws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(1);

await cws.ConnectAsync(uri, httpClient, cancellationToken);

HttpClientFactory nyní ve výchozím nastavení neprotokoluje hodnoty hlaviček.

LogLevel.Trace události zaprotokolované HttpClientFactory už ve výchozím nastavení neobsahují hodnoty záhlaví. Pomocí metody RedactLoggedHeaders můžete povolit protokolování hodnot pro konkrétní hlavičky.

Následující příklad rediguje všechny hlavičky s výjimkou uživatelského agenta.

services.AddHttpClient("myClient")
    .RedactLoggedHeaders(name => name != "User-Agent");

Další informace naleznete v tématu protokolování v HttpClientFactory ve výchozím nastavení nahrazuje hodnoty hlaviček zástupnými symboly.

Zamyšlení

Trvalé sestavy

Ve verzích .NET Core a .NET 5–8 byla podpora sestavení a generování metadat reflexe pro dynamicky vytvořené typy omezena na spustitelný AssemblyBuilder. Nedostatečná podpora saving sestavení byla často překážkou pro zákazníky, kteří migrují z .NET Framework na .NET. .NET 9 přidá nový typ PersistedAssemblyBuilder, který můžete použít k uložení generovaného sestavení.

Chcete-li vytvořit PersistedAssemblyBuilder instanci, zavolejte její konstruktor a předávejte název sestavení, základní sestavení System.Private.CoreLib pro odkazování na základní typy prostředí runtime, a volitelné vlastní atributy. Po emitování všech členů do sestavení zavolejte metodu PersistedAssemblyBuilder.Save(String), která vytvoří sestavení s výchozím nastavením. Pokud chcete nastavit vstupní bod nebo jiné možnosti, můžete volat PersistedAssemblyBuilder.GenerateMetadata a používat metadata, která se vrátí k uložení sestavení. Následující kód ukazuje příklad vytvoření trvalého sestavení a nastavení vstupního bodu.

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]));
}

Nová PersistedAssemblyBuilder třída obsahuje podporu PDB. Můžete vygenerovat informace o symbolech a použít je k ladění vygenerovaného sestavení. Rozhraní API má podobný tvar jako implementace .NET Framework. Další informace naleznete v tématu Generování symbolů a generování PDB.

Parsování názvu typu

TypeName je analyzátor pro názvy typů ECMA-335, který poskytuje mnohem stejné funkce jako System.Type v prostředí runtime, ale je oddělený od prostředí runtime. Komponenty, jako jsou serializátory a kompilátory, musí analyzovat a zpracovávat názvy typů. Například nativní kompilátor AOT přepnul na použití TypeName.

Nová TypeName třída poskytuje:

  • Statické Parse a TryParse metody pro analýzu vstupu reprezentované jako ReadOnlySpan<char>. Obě metody přijímají instanci TypeNameParseOptions třídy (taška možností), která umožňuje přizpůsobit analýzu.

  • Name, FullNamea AssemblyQualifiedName vlastnosti, které fungují přesně stejně jako jejich protějšky v System.Type.

  • Více vlastností a metod, které poskytují další informace o samotném názvu:

    • IsArray, IsSZArray (SZ zkratka pro jednorozměrné pole s nulovým indexováním) IsVariableBoundArrayType a GetArrayRank pro práci s poli.
    • IsConstructedGenericType, GetGenericTypeDefinitiona GetGenericArguments pro práci s obecnými názvy typů.
    • IsByRef a IsPointer pro práci s ukazateli a spravovanými odkazy.
    • GetElementType() pro práci s ukazateli, odkazy a poli.
    • IsNested a DeclaringType pro práci s vnořenými typy.
    • AssemblyName, který zveřejňuje informace o názvu sestavení prostřednictvím nové AssemblyNameInfo třídy. Na rozdíl od AssemblyName, nový typ je neměnný a analýza názvů jazykové verze nevytváří instance CultureInfo.

Oba TypeName typy AssemblyNameInfo jsou neměnné a neposkytují způsob kontroly rovnosti (neimplementují IEquatable). Porovnání názvů sestavení je jednoduché, ale různé scénáře musí porovnávat pouze podmnožinu vystavených informací (Name, Version, CultureNamea PublicKeyOrToken).

Následující fragment kódu ukazuje ukázkové použití.

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()}'");
    }
}

Nová rozhraní API jsou k dispozici v balíčku NuGet System.Reflection.Metadata, která je možné použít s verzemi .NET nižší úrovně.

Regulární výrazy

[GeneratedRegex] ve vlastnostech

.NET 7 zavedl generátor zdroje Regex a odpovídající atribut GeneratedRegexAttribute.

Následující částečná metoda bude generována se veškerým kódem potřebným k implementaci Regex.

[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();

C# 13 podporuje částečné properties kromě částečných metod, takže počínaje .NET 9 můžete také použít [GeneratedRegex(...)] u vlastnosti.

Následující částečná vlastnost je ekvivalentem vlastnosti předchozího příkladu.

[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }

Regex.EnumerateSplits

Třída Regex poskytuje metodu Split , podobně jako v konceptu metody String.Split . Pomocí String.Split dodáte jeden nebo více char nebo string oddělovačů a implementace rozdělí vstupní text podle těchto oddělovačů. Místo toho, aby se oddělovač zadával jako Regex.Split nebo char, se zadává jako vzor regulárního výrazu pomocí string.

Následující příklad ukazuje 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 Přijímá však pouze string jako vstup a nepodporuje zadávání vstupu jako .ReadOnlySpan<char> Také poskytuje úplnou sadu rozdělení jako string[], což vyžaduje přidělení jak pole string k uložení výsledků, tak string pro každé rozdělení. V .NET 9 nová metoda EnumerateSplits umožňuje provádět stejnou operaci, ale se vstupem založeným na rozsahu a bez jakéhokoli přidělení výsledků. Přijme ReadOnlySpan<char> a vrátí výčet Range objektů, které představují výsledky.

Následující příklad ukazuje Regex.EnumerateSplits, který bere ReadOnlySpan<char> jako vstup.

ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
    Console.WriteLine($"Split: \"{input[r]}\"");
}

Serializace (System.Text.Json)

Možnosti odsazení

JsonSerializerOptions obsahuje nové vlastnosti, které umožňují přizpůsobit odsazení a velikost odsazení zapsaného JSON.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

string json = JsonSerializer.Serialize(
    new { Value = 1 },
    options
    );
Console.WriteLine(json);
//{
//                "Value": 1
//}

Výchozí webové možnosti singleton

Pokud chcete serializovat pomocí možností default, které ASP.NET Core používá pro webové aplikace, použijte nový JsonSerializerOptions.Web singleton.

string webJson = JsonSerializer.Serialize(
    new { SomeValue = 42 },
    JsonSerializerOptions.Web // Defaults to camelCase naming policy.
    );
Console.WriteLine(webJson);
// {"someValue":42}

JsonSchemaExporter

JSON se často používá k reprezentaci typů v podpisech metod jako součást schémat vzdáleného volání procedur. Používá se například jako součást specifikací OpenAPI nebo jako součást volání nástrojů se službami AI, jako jsou služby OpenAI. Vývojáři mohou serializovat a deserializovat typy .NET jako JSON pomocí System.Text.Json. Musí ale mít také možnost získat schéma JSON, které popisuje tvar typu .NET (to znamená, popisuje tvar toho, co by bylo serializováno a co může být deserializováno). System.Text.Json nyní poskytuje typ JsonSchemaExporter, který podporuje generování schématu JSON představujícího typ .NET.

Další informace najdete v tématu Exportér schématu JSON.

Respektovat poznámky s možnou hodnotou null

System.Text.Json nyní rozpozná anotace nulovatelnosti vlastností a lze je nastavit k vynucení během serializace a deserializace pomocí příznaku RespectNullableAnnotations.

Následující kód ukazuje, jak nastavit možnost:

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; }
}

Další informace naleznete v tématu Pozor na anotace typu null.

Vyžadování nepovinných parametrů konstruktoru

V minulosti se System.Text.Json zacházelo s nepovinovými parametry konstruktoru jako volitelné při použití deserializace založené na konstruktoru. Toto chování můžete změnit použitím nové RespectRequiredConstructorParameters vlajky.

Následující kód ukazuje, jak nastavit možnost:

JsonSerializerOptions options = new() { RespectRequiredConstructorParameters = true };

// Throws exception: System.Text.Json.JsonException: JSON deserialization
// for type 'Serialization+MyPoco' was missing required properties including: 'Value'.
JsonSerializer.Deserialize<MyPoco>("""{}""", options);

Typ MyPoco je definován takto:

record MyPoco(string Value);

Další informace naleznete v tématu Povinné parametry konstruktoru.

Seřadit vlastnosti JsonObject

Typ JsonObject teď zveřejňuje uspořádaná slovníková rozhraní API, která umožňují explicitní manipulaci s pořadím vlastností.

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

Další informace naleznete v tématu Manipulace s pořadím vlastností.

Přizpůsobte názvy členů výčtu

Nový System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute atribut lze použít k přizpůsobení názvů jednotlivých členů výčtu pro typy, které jsou serializovány jako řetězce:

JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"

[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
    Value1 = 1,
    [JsonStringEnumMemberName("Custom enum value")]
    Value2 = 2,
}

Další informace naleznete v tématu Vlastní názvy členů výčtu.

Streamování více dokumentů JSON

System.Text.Json.Utf8JsonReader teď podporuje čtení více mezerou oddělených dokumentů JSON z jednoho vyrovnávací paměti nebo datového proudu. Ve výchozím nastavení čtečka vyvolá výjimku, pokud detekuje jakékoli nepřázdné znaky následující za prvním dokumentem nejvyšší úrovně. Toto chování můžete změnit pomocí příznaku AllowMultipleValues .

Další informace najdete v tématu Čtení více dokumentů JSON.

Rozpětí

Ve vysoce výkonném kódu se rozsahy často používají k tomu, aby nedocházelo k zbytečnému přidělování řetězců. Span<T> a ReadOnlySpan<T> nadále mění způsob psaní kódu v .NET a s každým vydáním se přidává stále více metod, které pracují s objekty typu span. .NET 9 zahrnuje následující aktualizace související s rozsahem:

Pomocníci souborů

Třída File teď má nové pomocné rutiny, které snadno a přímo zapisují ReadOnlySpan<char>/ReadOnlySpan<byte> a ReadOnlyMemory<char>/ReadOnlyMemory<byte> do souborů.

Následující kód efektivně zapíše ReadOnlySpan<char> do souboru.

ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);

Byly přidány také nové StartsWith<T>(ReadOnlySpan<T>, T) a EndsWith<T>(ReadOnlySpan<T>, T) rozšiřující metody pro rozsahy, což usnadňuje testování, zda ReadOnlySpan<T> začíná nebo končí konkrétní T hodnotou.

Následující kód používá tato nová rozhraní API pro pohodlí.

ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false

params ReadOnlySpan<T> Přetížení

Jazyk C# vždy podporuje označení parametrů pole jako params. Toto klíčové slovo umožňuje zjednodušenou syntaxi volání. Například druhý parametr metody String.Join(String, String[]) je označen pomocí params. Toto přetížení můžete volat pomocí pole nebo předáním jednotlivých hodnot:

string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");

Před .NET 9, když předáte hodnoty jednotlivě, kompilátor jazyka C# generuje kód identický s prvním voláním tím, že vytvoří implicitní pole kolem tří argumentů.

Počínaje jazykem C# 13 můžete použít params s libovolným argumentem, který lze vytvořit pomocí výrazu kolekce, včetně rozsahů (Span<T> a ReadOnlySpan<T>). To je výhodné pro použitelnost a výkon. Kompilátor jazyka C# může ukládat argumenty do zásobníku, zabalit je do rozsahu a předat to metodě, což zabrání implicitní alokaci pole, které by jinak vedlo.

.NET 9 zahrnuje více než 60 metod s parametrem params ReadOnlySpan<T>. Některé jsou zcela nové přetížení a některé jsou existující metody, které již podporovaly ReadOnlySpan<T>, ale nyní mají tento parametr označený params. Čistý efekt je, pokud upgradujete na .NET 9 a rekompilujete kód, uvidíte vylepšení výkonu bez provedení jakýchkoli změn kódu. Je to proto, že kompilátor preferuje vazbu na přetížení založená na rozsahu než na přetížení založená na poli.

Teď například String.Join obsahuje následující přetížení, které implementuje nový vzor: String.Join(String, ReadOnlySpan<String>)

Nyní je volání jako string.Join(", ", "a", "b", "c") provedeno, aniž by bylo nutné alokovat pole k předání argumentů "a", "b" a "c".

Enumerovat segmenty z ReadOnlySpan<char>.Split()

string.Split je vhodná metoda pro rychlé dělení řetězce s jedním nebo více zadanými oddělovači. Pro kód zaměřený na výkon však může být profil alokace string.Split omezující, protože přiděluje řetězec pro každou parsovanou komponentu a string[] k jejich uložení všech. Také nepracuje s rozsahy, takže pokud máte ReadOnlySpan<char>, musíte přidělit další řetězec, když ho převedete na řetězec, abyste na něm mohli zavolat string.Split.

V .NET 8 byla zavedena sada metod Split a SplitAny pro ReadOnlySpan<char>. Místo vrácení nové string[]metody přijímají cíl Span<Range> , do kterého se zapisují ohraničující indexy pro každou komponentu. Díky tomu je operace zcela bez alokace. Tyto metody jsou vhodné použít, pokud je počet rozsahů známý i malý.

V .NET 9 byly přidány nová přetížení Split a SplitAny, aby bylo možné přírůstkově analyzovat ReadOnlySpan<T> s a priori neznámý počet segmentů. Nové metody umožňují procházet jednotlivé segmenty, které jsou podobně reprezentovány jako Range a mohou být použity pro rozřezání původního rozsahu.

public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
    foreach (Range segment in span.Split(','))
    {
        if (span[segment].SequenceEquals(item))
        {
            return true;
        }
    }

    return false;
}

System.Formats

Pozice nebo posun dat v uzavřeném datovém proudu objektu TarEntry je nyní veřejná vlastnost. TarEntry.DataOffset vrátí pozici v archivním datovém proudu položky, kde se nachází první bajt položky. Data záznamu jsou zapouzdřena v podstreamu, ke kterému máte přístup, což skryje skutečnou pozici dat vzhledem k archivačnímu streamu pomocí TarEntry.DataStream. To stačí pro většinu uživatelů, ale pokud potřebujete větší flexibilitu a chcete znát skutečnou počáteční pozici dat v archivačním streamu, nové TarEntry.DataOffset rozhraní API usnadňuje podporu funkcí, jako je souběžný přístup k velmi velkým souborům TAR.

// Create stream for tar ball data in Azure Blob Storage.
BlobClient blobClient = new(connectionString, blobContainerName, blobName);
Stream blobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);

// Create TarReader for the stream and get a TarEntry.
TarReader tarReader = new(blobClientStream);
System.Formats.Tar.TarEntry? tarEntry = await tarReader.GetNextEntryAsync();

if (tarEntry is null)
    return;

// Get position of TarEntry data in blob stream.
long entryOffsetInBlobStream = tarEntry.DataOffset;
long entryLength = tarEntry.Length;

// Create a separate stream.
Stream newBlobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
newBlobClientStream.Seek(entryOffsetInBlobStream, SeekOrigin.Begin);

// Read tar ball content from separate BlobClient stream.
byte[] bytes = new byte[entryLength];
await newBlobClientStream.ReadExactlyAsync(bytes, 0, (int)entryLength);

System.Guid

NewGuid() Guid vytvoří objekt, který je většinou vyplněn kryptograficky zabezpečenými náhodnými daty podle specifikace UUID verze 4 v RFC 9562. Stejný dokument RFC také definuje další verze, včetně verze 7, která "obsahuje časově uspořádané pole hodnoty odvozené od široce implementovaného a dobře známého zdroje časového razítka Unix Epoch". Jinými slovy, většina dat je stále náhodná, ale některá z nich jsou vyhrazena pro data na základě časového razítka, které těmto hodnotám umožňuje mít přirozené pořadí řazení. V .NET 9 můžete vytvořit Guid podle verze 7 prostřednictvím nových metod Guid.CreateVersion7() a Guid.CreateVersion7(DateTimeOffset). Vlastnost Version můžete také použít k načtení pole Guid verze objektu.

System.IO

Komprese pomocí zlib-ng

System.IO.Compression funkce jako ZipArchive, DeflateStream, GZipStreama ZLibStream jsou založeny především na knihovně zlib. Počínaje .NET 9 tyto funkce místo toho používají zlib-ng, knihovnu, která poskytuje konzistentnější a efektivnější zpracování v širší škále operačních systémů a hardwaru.

Možnosti komprese ZLib a Brotli

ZLibCompressionOptions a BrotliCompressionOptions jsou nové typy pro nastavení úrovně komprese a strategie specifické pro algoritmus (Default, Filtered, HuffmanOnly, RunLengthEncoding, nebo Fixed). Tyto typy jsou zaměřené na uživatele, kteří chtějí jemněji vyladěná nastavení než jediná existující možnost <System.IO.Compression.CompressionLevel>.

Nové typy možností komprese mohou být v budoucnu rozšířeny.

Následující fragment kódu ukazuje ukázkové použití:

private MemoryStream CompressStream(Stream uncompressedStream)
{
    MemoryStream compressorOutput = new();
    using ZLibStream compressionStream = new(
        compressorOutput,
        new ZLibCompressionOptions()
        {
            CompressionLevel = 6,
            CompressionStrategy = ZLibCompressionStrategy.HuffmanOnly
        }
        );
    uncompressedStream.CopyTo(compressionStream);
    compressionStream.Flush();

    return compressorOutput;
}

Dokumenty XPS z virtuální tiskárny XPS

Dokumenty XPS pocházející z virtuální tiskárny V4 XPS se dříve nedají otevřít pomocí System.IO.Packaging knihovny, a to kvůli nedostatku podpory pro zpracování souborů .piece . Tato mezera byla vyřešena v .NET 9.

System.Numerics

Horní limit bigInteger

BigInteger podporuje reprezentaci celočíselné hodnoty v podstatě libovolné délky. V praxi je však délka omezena omezeními základního počítače, jako je dostupná paměť nebo jak dlouho by výpočet daného výrazu trvalo. Kromě toho existují některá rozhraní API, která selžou vzhledem ke vstupům, které vedou k příliš velké hodnotě. Vzhledem k těmto omezením .NET 9 vynucuje maximální délku BigInteger, což znamená, že nesmí obsahovat více než (2^31) - 1 (přibližně 2,14 miliardy bitů). Takové číslo představuje téměř 256 MB přidělení a obsahuje přibližně 646,5 milionu číslic. Tento nový limit zajišťuje, že se všechna vystavená rozhraní API dobře chovají a konzistentně a zároveň umožňují čísla, která jsou daleko nad rámec většiny scénářů použití.

BigMul Rozhraní API

BigMul je operace, která vytvoří celý součin dvou čísel. .NET 9 přidá vyhrazená rozhraní API BigMul na int, long, uint a ulong jejichž návratový typ je další větší typ integer než typy parametrů.

Nová rozhraní API jsou:

Rozhraní API pro převod vektorů

.NET 9 přidá vyhrazená rozhraní API rozšíření pro převod mezi Vector2, Vector3, Vector4, Quaternion a Plane.

Nová rozhraní API jsou následující:

U převodů stejné velikosti, například mezi Vector4, Quaterniona Plane, jsou tyto převody nulové náklady. Totéž lze říci pro zužující převody, například z Vector4 do Vector2 nebo Vector3. Pro rozšiřující převody, například z Vector2 nebo Vector3 do Vector4, existuje normální rozhraní API, které inicializuje nové prvky na hodnotu 0, a Unsafe příponové rozhraní API, které tyto nové prvky ponechá nedefinované, a proto mohou být nulové náklady.

Rozhraní API k vytváření vektorů

K dispozici jsou nová Create rozhraní API pro Vector, Vector2, Vector3 a Vector4, která odpovídají ekvivalentním rozhraním API zpřístupněným pro typy hardwarových vektorů ve jmenném prostoru System.Runtime.Intrinsics.

Další informace o nových rozhraních API najdete tady:

Tato rozhraní API jsou primárně určena pro usnadnění a celkovou konzistenci u .NET typů akcelerovaných SIMD.

Další akcelerace

Byla provedena další vylepšení výkonu mnoha typů v oboru názvů System.Numerics, včetně BigInteger, Vector2, Vector3, Vector4, Quaternion a Plane.

V některých případech to vedlo ke zrychlení 2 až 5x na základní rozhraní API, včetně Matrix4x4 násobení, vytvoření Plane ze řady vrcholů, Quaternion zřetězení a výpočtu křížového součinu Vector3.

K dispozici je také podpora konstantního skládání pro rozhraní SinCos API, které počítá jak Sin(x), tak Cos(x) v jednom volání, což činí proces efektivnějším.

Tenzory pro umělou inteligenci

Tensory jsou základní datovou strukturou umělé inteligence (AI). Často si je lze představit jako multidimenzionální pole.

Tenzory se používají k:

  • Představují a kódují data, jako jsou textové sekvence (tokeny), obrázky, video a zvuk.
  • Efektivní manipulace s daty s vyššími rozměry
  • Efektivní použití výpočtů na data vyšších dimenzí.
  • Ukládání informací o hmotnosti a průběžných výpočtů (v neurálních sítích)

Pokud chcete použít rozhraní API .NET tensorů, nainstalujte balíček NuGet System.Numerics.Tensors.

Nový typ Tensor<T>

Nový typ Tensor<T> rozšiřuje možnosti AI knihoven .NET a modulu runtime. Tento typ:

  • Poskytuje efektivní spolupráci s knihovnami AI, jako jsou ML.NET, TorchSharp a ONNX Runtime, s využitím nulových kopií, pokud je to možné.
  • Vychází z TensorPrimitives pro efektivní matematické operace.
  • Umožňuje snadnou a efektivní manipulaci s daty tím, že poskytuje operace indexování a řezů.
  • Nenahradí stávající knihovny umělé inteligence a strojového učení. Místo toho má poskytnout společnou sadu rozhraní API pro omezení duplikace kódu a závislostí a dosažení lepšího výkonu pomocí nejnovějších funkcí modulu runtime.

Následující kódy ukazují některá z rozhraní API, která jsou součástí nového Tensor<T> typu.

// Create a tensor (1 x 3).
Tensor<int> t0 = Tensor.Create([1, 2, 3], [1, 3]); // [[1, 2, 3]]

// Reshape tensor (3 x 1).
Tensor<int> t1 = t0.Reshape(3, 1); // [[1], [2], [3]]

// Slice tensor (2 x 1).
Tensor<int> t2 = t1.Slice(1.., ..); // [[2], [3]]

// Broadcast tensor (3 x 1) -> (3 x 3).
// [
//  [ 1, 1, 1],
//  [ 2, 2, 2],
//  [ 3, 3, 3]
// ]
var t3 = Tensor.Broadcast<int>(t1, [3, 3]);

// Math operations.
var t4 = Tensor.Add(t0, 1); // [[2, 3, 4]]
var t5 = Tensor.Add(t0.AsReadOnlyTensorSpan(), t0); // [[2, 4, 6]]
var t6 = Tensor.Subtract(t0, 1); // [[0, 1, 2]]
var t7 = Tensor.Subtract(t0.AsReadOnlyTensorSpan(), t0); // [[0, 0, 0]]
var t8 = Tensor.Multiply(t0, 2); // [[2, 4, 6]]
var t9 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t0); // [[1, 4, 9]]
var t10 = Tensor.Divide(t0, 2); // [[0.5, 1, 1.5]]
var t11 = Tensor.Divide(t0.AsReadOnlyTensorSpan(), t0); // [[1, 1, 1]]

Poznámka:

Toto rozhraní API je označené jako experimental pro .NET 9.

Tenzorové primitiva

Knihovna System.Numerics.Tensors obsahuje TensorPrimitives třídu, která poskytuje statické metody pro provádění numerických operací s rozsahy hodnot. V .NET 9 se rozsah metod vystavených TensorPrimitives výrazně rozšířil, z 40 (v .NET 8) na téměř 200 přetížení. Povrchová oblast zahrnuje známé číselné operace z typů jako Math a MathF. Zahrnuje také obecná matematická rozhraní, jako je INumber<TSelf>, s výjimkou toho, že místo zpracování individuální hodnoty zpracovávají řadu hodnot. Mnoho operací se také urychlilo prostřednictvím implementací optimalizovaných pro SIMD pro .NET 9.

TensorPrimitives nyní zveřejňuje obecná přetížení pro libovolný typ T , který implementuje určité rozhraní. (Verze .NET 8 zahrnovala pouze přetížení pro manipulaci s rozsahy float hodnot.) Například nové přetížení CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) provádí kosinovou podobnost na dvou vektorech float, double nebo Half hodnot, nebo na hodnotách jakéhokoli jiného typu, který implementuje IRootFunctions<TSelf>.

Porovnejte přesnost operace podobnosti kosinu u dvou vektorů typu float a double:

ReadOnlySpan<float> vector1 = [1, 2, 3];
ReadOnlySpan<float> vector2 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector1, vector2));
// Prints 0.9746318

ReadOnlySpan<double> vector3 = [1, 2, 3];
ReadOnlySpan<double> vector4 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector3, vector4));
// Prints 0.9746318461970762

Vlákna

Rozhraní API pro podprocesy zahrnují vylepšení iterace prostřednictvím úloh, pro kanály s prioritami, které mohou uspořádat své prvky místo toho, aby fungovaly na principu FIFO (first-in-first-out) a Interlocked.CompareExchange pro více typů.

Task.WhenEach

Pro práci s Task<TResult> objekty jsme přidali řadu užitečných nových rozhraní API. Nová Task.WhenEach metoda umožňuje iterovat úkoly při jejich dokončení pomocí await foreach příkazu. Už nemusíte dělat věci, jako je opakované volání Task.WaitAny sady úkolů, abyste si vybrali další úkol, který se dokončí.

Následující kód provádí několik HttpClient volání a pracuje s jejich výsledky při jejich dokončení.

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);
}

Prioritní nevázaný kanál

Obor názvů System.Threading.Channels umožňuje vytvářet kanály typu FIFO (first-in-first-out) pomocí metod CreateBounded a CreateUnbounded. U kanálů FIFO se prvky čtou z kanálu v pořadí, v jakém byly do kanálu zapsány. V .NET 9 byla přidána nová metoda CreateUnboundedPrioritized, která objedná prvky tak, aby další prvek přečtený z kanálu je ten, který se považuje za nejdůležitější podle Comparer<T>.Default nebo vlastního IComparer<T>.

Následující příklad používá novou metodu k vytvoření kanálu, který vypíše čísla 1 až 5 v pořadí, i když jsou zapsány do kanálu v jiném pořadí.

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 pro více typů

V předchozích verzích .NET měly Interlocked.Exchange a Interlocked.CompareExchange přetížení pro práci s int, uint, long, ulong, nint, nuint, float, double a object a také obecné přetížení pro práci s libovolným referenčním typem T. V .NET 9 existují nová přetížení pro atomickou práci s byte, sbyte, short a ushort. Také obecné omezení obecného Interlocked.Exchange<T> a Interlocked.CompareExchange<T> přetížení bylo odebráno, takže tyto metody již nejsou omezeny pouze na práci s odkazovými typy. Nyní mohou pracovat s libovolným primitivním typem, který zahrnuje všechny výše uvedené typy a bool také , charstejně jako jakýkoli enum typ.