Nyheter i .NET 9

Lär dig mer om de nya funktionerna i .NET 9 och hitta länkar till ytterligare dokumentation.

.NET 9, efterföljare till .NET 8, har ett särskilt fokus på molnbaserade appar och prestanda. Det kommer att stödjas i 18 månader som en sts-version (standard-term support). Du kan ladda ned .NET 9 här.

Teknikteamet är nytt för .NET 9 och publicerar förhandsversionsuppdateringar för .NET 9 på GitHub Discussions. Det är ett bra ställe att ställa frågor och ge feedback om versionen.

Den här artikeln har uppdaterats för .NET 9 Preview 2. I följande avsnitt beskrivs uppdateringarna av .NET-kärnbiblioteken i .NET 9.

.NET-körning

Serialization

I System.Text.Jsonhar .NET 9 nya alternativ för serialisering av JSON och en ny singleton som gör det enklare att serialisera med webbstandarder.

Indragsalternativ

JsonSerializerOptions innehåller nya egenskaper som gör att du kan anpassa indragstecknet och indragsstorleken för skrivet JSON.

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

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

Standardwebbalternativ

Om du vill serialisera med standardalternativen som ASP.NET Core använder för webbappar använder du den nya JsonSerializerOptions.Web singletonen.

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

LINQ

Nya metoder CountBy och AggregateBy har introducerats. Dessa metoder gör det möjligt att aggregera tillstånd efter nyckel utan att behöva allokera mellanliggande grupper via GroupBy.

CountBy gör att du snabbt kan beräkna frekvensen för varje nyckel. I följande exempel hittar du det ord som förekommer oftast i en textsträng.

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 gör att du kan implementera fler arbetsflöden för generell användning. I följande exempel visas hur du kan beräkna poäng som är associerade med en viss nyckel.

(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>) gör det möjligt att snabbt extrahera det implicita indexet för en uppräkningsbar. Nu kan du skriva kod som följande kodfragment för att automatiskt indexera objekt i en samling.

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

Samlingar

Samlingstypen PriorityQueue<TElement,TPriority> i System.Collections.Generic namnområdet innehåller en ny Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) metod som du kan använda för att uppdatera prioriteten för ett objekt i kön.

PriorityQueue.Remove()-metod

.NET 6 introducerade PriorityQueue<TElement,TPriority> samlingen, som ger en enkel och snabb implementering av matris-heap. Ett problem med matrishögar i allmänhet är att de inte stöder prioritetsuppdateringar, vilket gör dem oöverkomliga för användning i algoritmer som varianter av Dijkstras algoritm.

Även om det inte går att implementera effektiva $O(\log n)$ prioritetsuppdateringar i den befintliga samlingen, gör den nya PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) metoden det möjligt att emulera prioritetsuppdateringar (om än vid $O(n)$ tid):

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

Den här metoden avblockerar användare som vill implementera grafalgoritmer i kontexter där asymptotisk prestanda inte är en blockerare. (Sådana sammanhang omfattar utbildning och prototyper.) Här är till exempel en leksaksimplementering av Dijkstras algoritm som använder det nya API:et.

Kryptografi

För kryptografi lägger .NET 9 till en ny hash-metod med ett skott på CryptographicOperations typen. Den lägger också till nya klasser som använder KMAC-algoritmen.

CryptographicOperations.HashData()-metod

.NET innehåller flera statiska "one-shot" -implementeringar av hashfunktioner och relaterade funktioner. Dessa API:er inkluderar SHA256.HashData och HMACSHA256.HashData. One-shot-API:er är att föredra att använda eftersom de kan ge bästa möjliga prestanda och minska eller eliminera allokeringar.

Om en utvecklare vill tillhandahålla ett API som stöder hashning där anroparen definierar vilken hashalgoritm som ska användas, görs det vanligtvis genom att acceptera ett HashAlgorithmName argument. Men om du använder det mönstret med enstaka API:er måste du växla över alla möjliga och HashAlgorithmName sedan använda lämplig metod. För att lösa det problemet introducerar .NET 9 API:et CryptographicOperations.HashData . Med det här API:et kan du skapa en hash eller HMAC över en indata som en enda bild där algoritmen som används bestäms av en HashAlgorithmName.

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

KMAC-algoritm

.NET 9 tillhandahåller KMAC-algoritmen enligt NIST SP-800-185. KECCAK Message Authentication Code (KMAC) är en pseudorandomfunktion och nyckelad hash-funktion baserad på KECCAK.

Följande nya klasser använder KMAC-algoritmen. Använd instanser för att samla in data för att skapa en MAC eller använda den statiska HashData metoden för ett enda skott över en enda indata.

KMAC är tillgängligt i Linux med OpenSSL 3.0 eller senare och i Windows 11 Build 26016 eller senare. Du kan använda den statiska IsSupported egenskapen för att avgöra om plattformen stöder den önskade algoritmen.

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

Reflektion

I .NET Core-versioner och .NET 5-8 begränsades stöd för att skapa en sammansättning och generera reflektionsmetadata för dynamiskt skapade typer till en runnable AssemblyBuilder. Bristen på stöd för att spara en sammansättning var ofta en blockerare för kunder som migrerade från .NET Framework till .NET. .NET 9 lägger till offentliga API:er för AssemblyBuilder att spara en genererad sammansättning.

Den nya, beständiga AssemblyBuilder implementeringen är körning och plattformsoberoende. Om du vill skapa en bevarad AssemblyBuilder instans använder du det nya AssemblyBuilder.DefinePersistedAssembly API:et. Det befintliga AssemblyBuilder.DefineDynamicAssembly API:et accepterar sammansättningsnamnet och valfria anpassade attribut. Om du vill använda det nya API:et skickar du kärnsammansättningen, , System.Private.CoreLibsom används för att referera till baskörningstyper. Det finns inget alternativ för AssemblyBuilderAccess. Och för tillfället stöder den bevarade AssemblyBuilder implementeringen endast sparande, inte körning. När du har skapat en instans av den bevarade AssemblyBuilderförblir de efterföljande stegen för att definiera en modul, typ, metod eller uppräkning, skrivning av IL och alla andra användningar oförändrade. Det innebär att du kan använda befintlig System.Reflection.Emit kod som den är för att spara sammansättningen. Följande kod visar ett exempel.

public void CreateAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
        new AssemblyName("MyAssembly"),
        typeof(object).Assembly
        );
    TypeBuilder tb = ab.DefineDynamicModule("MyModule")
        .DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder mb = tb.DefineMethod(
        "SumMethod",
        MethodAttributes.Public | MethodAttributes.Static,
        typeof(int), [typeof(int), typeof(int)]
        );
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();
    ab.Save(assemblyPath); // or could save to a Stream
}

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

Prestanda

.NET 9 innehåller förbättringar av 64-bitars JIT-kompilatorn som syftar till att förbättra appprestanda. Dessa förbättringar av kompilatorn är:

Arm64-vektorisering är en annan ny funktion i körningen.

Loopoptimeringar

Att förbättra kodgenereringen för loopar är en prioritet för .NET 9, och 64-bitars kompilatorn har en ny optimering som kallas induktionsvariabel (IV) breddning.

En IV är en variabel vars värde ändras när den innehållande loopen itereras. I följande for loop i är en IV: for (int i = 0; i < 10; i++). Om kompilatorn kan analysera hur ett IV-värde utvecklas över loopens iterationer kan den skapa mer högpresterande kod för relaterade uttryck.

Tänk dig följande exempel som itererar via en matris:

static int Sum(int[] arr)
{
    int sum = 0;
    for (int i = 0; i < arr.Length; i++)
    {
        sum += arr[i];
    }

    return sum;
}

Indexvariabeln, i, är 4 byte i storlek. På sammansättningsnivå används 64-bitarsregister vanligtvis för att lagra matrisindex på x64, och i tidigare .NET-versioner genererade kompilatorn kod som noll utökades i till 8 byte för matrisåtkomsten, men fortsatte att behandlas i som ett heltal på 4 byte någon annanstans. För att i utöka till 8 byte krävs dock ytterligare en instruktion på x64. Med IV-breddning utökas i nu 64-bitars JIT-kompilatorn till 8 byte i hela slingan, vilket utelämnar nolltillägget. Det är mycket vanligt att loopa över matriser, och fördelarna med den här instruktionsborttagningen läggs snabbt ihop.

Inliningsförbättringar för intern AOT

En av . NET:s mål för 64-bitars JIT-kompilatorns inliner är att ta bort så många begränsningar som möjligt som hindrar en metod från att infogas. .NET 9 möjliggör inlining av åtkomst till trådlokal statiska objekt i Windows x64, Linux x64 och Linux Arm64.

För static klassmedlemmar finns exakt en instans av medlemmen i alla instanser av klassen, som "delar" medlemmen. Om värdet för en static medlem är unikt för varje tråd kan det värdet trådlokalt förbättra prestanda, eftersom det eliminerar behovet av en samtidig primitiv för att på ett säkert sätt komma åt static medlemmen från dess innehållande tråd.

Tidigare krävde åtkomst till trådlokal statiska objekt i interna AOT-kompilerade program 64-bitars JIT-kompilatorn för att generera ett anrop till körningen för att hämta basadressen för den trådlokala lagringen. Nu kan kompilatorn infoga dessa anrop, vilket resulterar i mycket färre instruktioner för att komma åt dessa data.

PGO-förbättringar: Skriv kontroller och casts

.NET 8-aktiverad dynamisk profilstyrd optimering (PGO) som standard. NET 9 expanderar 64-bitars JIT-kompilatorns PGO-implementering för att profilera fler kodmönster. När nivåindelad kompilering är aktiverad infogar 64-bitars JIT-kompilatorn redan instrumentation i programmet för att profilera dess beteende. När den kompileras om med optimeringar använder kompilatorn den profil som den skapade vid körning för att fatta beslut som är specifika för den aktuella körningen av ditt program. I .NET 9 använder 64-bitars JIT-kompilatorn PGO-data för att förbättra prestanda för typkontroller.

För att fastställa typen av ett objekt krävs ett anrop till körningen, vilket medför en prestandastraff. När typen av ett objekt måste kontrolleras genererar 64-bitars JIT-kompilatorn det här anropet för korrekthetens skull (kompilatorer kan vanligtvis inte utesluta några möjligheter, även om de verkar osannolika). Men om PGO-data tyder på att ett objekt sannolikt är en viss typ, genererar 64-bitars JIT-kompilatorn nu en snabb sökväg som billigt söker efter den typen och faller tillbaka på den långsamma anropsvägen till körningen endast om det behövs.

Arm64-vektorisering i .NET-bibliotek

En ny EncodeToUtf8 implementering drar nytta av 64-bitars JIT-kompilatorns möjlighet att generera instruktioner för belastning/lagring i flera register på Arm64. Med det här beteendet kan program bearbeta större datasegment med färre instruktioner. .NET-appar i olika domäner bör se dataflödesförbättringar på Arm64-maskinvara som stöder dessa funktioner. Vissa riktmärken minskar sin körningstid med mer än hälften.

.NET SDK

Enhetstestning

I det här avsnittet beskrivs uppdateringar av enhetstestning i .NET 9: köra tester parallellt och utdata från terminalloggningstest.

Köra tester parallellt

I .NET 9 dotnet test är det mer fullständigt integrerat med MSBuild. Eftersom MSBuild har stöd för att bygga parallellt kan du köra tester för samma projekt i olika målramverk parallellt. Som standard begränsar MSBuild antalet parallella processer till antalet processorer på datorn. Du kan också ange en egen gräns med växeln -maxcpucount . Om du vill avanmäla dig från parallelliteten anger du TestTfmsInParallel egenskapen MSBuild till false.

Testvisning för terminalloggare

Testresultatrapportering för dotnet test stöds nu direkt i MSBuild-terminalloggaren. Du får mer fullständig testrapportering både när testerna körs (visar testnamnet som körs) och när testerna har slutförts (eventuella testfel återges på ett bättre sätt).

Mer information om terminalloggaren finns i dotnet-byggalternativ.

.NET-verktyget rullar framåt

.NET-verktyg är ramverksberoende appar som du kan installera globalt eller lokalt och sedan köra med .NET SDK och installerade .NET-runtimes. Dessa verktyg, precis som alla .NET-appar, riktar sig till en specifik huvudversion av .NET. Som standard körs inte appar på nyare versioner av .NET. Verktygsförfattare har valt att köra sina verktyg på nyare versioner av .NET-körningen genom att ange RollForward egenskapen MSBuild. Men det gör inte alla verktyg.

Ett nytt alternativ för dotnet tool install låter användare bestämma hur .NET-verktyg ska köras. När du installerar ett verktyg via dotnet tool installeller när du kör verktyget via dotnet tool run <toolname>kan du ange en ny flagga med namnet --allow-roll-forward. Det här alternativet konfigurerar verktyget med framåtrullningsläge Major. Med det här läget kan verktyget köras på en nyare huvudversion av .NET om den matchande .NET-versionen inte är tillgänglig. Den här funktionen hjälper tidiga användare att använda .NET-verktyg utan att verktygsförfattare behöver ändra någon kod.

Se även