Co nowego w środowisku uruchomieniowym platformy .NET 8

W tym artykule opisano nowe funkcje w środowisku uruchomieniowym platformy .NET dla platformy .NET 8.

usprawnienia dotyczące wydajności

Platforma .NET 8 zawiera ulepszenia generowania kodu i kompilacji just in time (JIT):

  • Ulepszenia wydajności arm64
  • Ulepszenia SIMD
  • Obsługa rozszerzeń ISA AVX-512 (zobacz Vector512 i AVX-512)
  • Ulepszenia natywne dla chmury
  • Ulepszenia przepływności JIT
  • Pętla i ogólne optymalizacje
  • Zoptymalizowany dostęp dla pól oznaczonych za pomocą polecenia ThreadStaticAttribute
  • Kolejna alokacja rejestru. Arm64 ma dwie instrukcje dotyczące wyszukiwania wektorów tabeli, które wymagają, aby wszystkie jednostki w operandach krotki znajdują się w kolejnych rejestrach.
  • Funkcja JIT/NativeAOT może teraz wyrejestrować i automatycznie wektoryzować niektóre operacje pamięci za pomocą funkcji SIMD, takich jak porównanie, kopiowanie i zerowanie, jeśli może określić ich rozmiary w czasie kompilacji.

Ponadto optymalizacja dynamiczna sterowana profilem (PGO) została ulepszona i jest teraz domyślnie włączona. Aby ją włączyć, nie trzeba już używać opcji konfiguracji środowiska uruchomieniowego. Dynamiczne rozwiązanie PGO działa ręcznie z kompilacją warstwową w celu dalszej optymalizacji kodu na podstawie dodatkowej instrumentacji wprowadzonej w warstwie 0.

Średnio dynamiczne PGO zwiększa wydajność o około 15%. W zestawie porównawczym ok. 4600 testów 23% odnotowało poprawę wydajności 20% lub więcej.

Promocja struktury Codegen

Platforma .NET 8 zawiera nowy fizyczny kodator optymalizacji podwyższania poziomu, który uogólnia zdolność JIT do promowania zmiennych struktury. Ta optymalizacja (nazywana również scalarną zamianą agregacji) zastępuje pola zmiennych struktury zmiennymi pierwotnymi, których następnie można użyć do rozumowania i optymalizacji bardziej precyzyjnej.

Tryb JIT już obsługiwał tę optymalizację, ale z kilkoma dużymi ograniczeniami, w tym:

  • Była obsługiwana tylko w przypadku struktur z czterema lub mniejszą liczbą pól.
  • Obsługiwane było tylko wtedy, gdy każde pole było typem pierwotnym lub prostą strukturą opakowującym typ pierwotny.

Podwyższenie poziomu fizycznego usuwa te ograniczenia, co rozwiązuje szereg długotrwałych problemów z dostępem JIT.

Wyrzucanie elementów bezużytecznych

Platforma .NET 8 dodaje możliwość dostosowania limitu pamięci na bieżąco. Jest to przydatne w scenariuszach usług w chmurze, w których zapotrzebowanie przychodzi i idzie. Aby zapewnić ekonomiczne koszty, usługi powinny być skalowane w górę i w dół przy użyciu zasobów w miarę wahania się zapotrzebowania. Gdy usługa wykryje spadek zapotrzebowania, może skalować zużycie zasobów w dół przez zmniejszenie limitu pamięci. Wcześniej nie powiodło się to, ponieważ moduł odśmiecenia pamięci (GC) nie wiedział o zmianie i może przydzielić więcej pamięci niż nowy limit. Dzięki tej zmianie możesz wywołać RefreshMemoryLimit() interfejs API, aby zaktualizować GC przy użyciu nowego limitu pamięci.

Należy pamiętać o pewnych ograniczeniach:

  • Na platformach 32-bitowych (na przykład Windows x86 i Linux ARM) platforma .NET nie może ustanowić nowego limitu twardego sterty, jeśli jeszcze go nie ma.
  • Interfejs API może zwrócić kod stanu inny niż zero wskazujący, że odświeżanie nie powiodło się. Może się tak zdarzyć, jeśli skala w dół jest zbyt agresywna i nie pozostawia miejsca na manewr GC. W takim przypadku rozważ wywołanie GC.Collect(2, GCCollectionMode.Aggressive) metody w celu zmniejszenia bieżącego użycia pamięci, a następnie spróbuj ponownie.
  • Jeśli przeskalujesz limit pamięci w górę poza rozmiar, który GC uważa, że proces może obsłużyć podczas uruchamiania, RefreshMemoryLimit wywołanie powiedzie się, ale nie będzie w stanie użyć więcej pamięci niż to, co postrzega jako limit.

Poniższy fragment kodu pokazuje, jak wywołać interfejs API.

GC.RefreshMemoryLimit();

Możesz również odświeżyć niektóre ustawienia konfiguracji GC związane z limitem pamięci. Poniższy fragment kodu ustawia twardy limit sterty do 100 mebibajtów (MiB):

AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();

Interfejs API może zgłosić InvalidOperationException , jeśli limit twardy jest nieprawidłowy, na przykład w przypadku ujemnych wartości procentowych limitu twardego sterty i jeśli limit twardy jest zbyt niski. Może się tak zdarzyć, jeśli ustalony limit sterty, który zostanie ustawiony z powodu nowych ustawień usługi AppData lub dorozumianych przez zmiany limitu pamięci kontenera, jest niższy niż to, co zostało już zatwierdzone.

Globalizacja dla aplikacji mobilnych

Aplikacje mobilne w systemach iOS, tvOS i MacCatalyst mogą zdecydować się na nowy hybrydowy tryb globalizacji korzystający z lżejszego pakietu ICU. W trybie hybrydowym dane globalizacji są częściowo pobierane z pakietu ICU i częściowo z wywołań do natywnych interfejsów API. Tryb hybrydowy obsługuje wszystkie ustawienia regionalne obsługiwane przez urządzenia przenośne.

Tryb hybrydowy jest najbardziej odpowiedni dla aplikacji, które nie mogą działać w niezmiennym trybie globalizacji i używają kultur, które zostały przycięte z danych ICU na urządzeniach przenośnych. Można go również użyć, gdy chcesz załadować mniejszy plik danych OIOM. (Plik icudt_hybrid.dat jest o 34,5 % mniejszy niż domyślny plik danych ICU icudt.dat).

Aby użyć trybu globalizacji hybrydowej HybridGlobalization , ustaw właściwość MSBuild na true:

<PropertyGroup>
  <HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

Należy pamiętać o pewnych ograniczeniach:

  • Ze względu na ograniczenia interfejsu API natywnego nie wszystkie interfejsy API globalizacji są obsługiwane w trybie hybrydowym.
  • Niektóre z obsługiwanych interfejsów API mają inne zachowanie.

Aby sprawdzić, czy aplikacja ma wpływ, zobacz Różnice behawioralne.

Międzyoperajności modelu COM generowanego przez źródło

Platforma .NET 8 zawiera nowy generator źródła, który obsługuje współdziałanie z interfejsami COM. Możesz użyć GeneratedComInterfaceAttribute elementu , aby oznaczyć interfejs jako interfejs COM dla generatora źródłowego. Następnie generator źródła wygeneruje kod umożliwiający wywoływanie z kodu C# do niezarządzanego kodu. Generuje również kod umożliwiający wywoływanie z niezarządzanego kodu w języku C#. Ten generator źródła integruje się z elementem LibraryImportAttributei można używać typów z parametrami i zwracanymi typami GeneratedComInterfaceAttribute w LibraryImportmetodach -atrybutów.

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
    void DoWork();
}

internal partial class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface);
}

Generator źródła obsługuje również nowy GeneratedComClassAttribute atrybut, aby umożliwić przekazywanie typów implementujących interfejsy z atrybutem GeneratedComInterfaceAttribute do niezarządzanych kodów. Generator źródła wygeneruje kod niezbędny do uwidocznienia obiektu COM, który implementuje interfejsy i przekazuje wywołania do implementacji zarządzanej.

Metody w interfejsach z atrybutem GeneratedComInterfaceAttribute obsługują wszystkie te same typy co LibraryImportAttribute, a LibraryImportAttribute teraz obsługuje GeneratedComInterfacetypy -atrybuty i GeneratedComClass-atrybuty.

Jeśli kod w języku C# używa tylko interfejsu GeneratedComInterface-attribute, aby opakowować obiekt COM z niezarządzanego kodu lub opakowować zarządzany obiekt z języka C#, aby uwidocznić niezarządzany kod, możesz użyć opcji we Options właściwości, aby dostosować kod, który zostanie wygenerowany. Te opcje oznaczają, że nie trzeba pisać marshallers dla scenariuszy, które wiesz, że nie będą używane.

Generator źródła używa nowego StrategyBasedComWrappers typu do tworzenia otoek obiektów COM i otoki obiektów zarządzanych oraz zarządzania nimi. Ten nowy typ obsługuje zapewnienie oczekiwanego środowiska użytkownika platformy .NET dla międzyoperacyjności modelu COM, zapewniając jednocześnie punkty dostosowywania dla zaawansowanych użytkowników. Jeśli aplikacja ma własny mechanizm definiowania typów z modelu COM lub jeśli chcesz obsługiwać scenariusze, które nie są obecnie obsługiwane przez wygenerowany źródło com, rozważ użycie nowego StrategyBasedComWrappers typu, aby dodać brakujące funkcje dla danego scenariusza i uzyskać to samo środowisko użytkownika platformy .NET dla typów COM.

Jeśli używasz programu Visual Studio, nowe analizatory i poprawki kodu ułatwiają konwertowanie istniejącego kodu międzyoperacyjności modelu COM w celu użycia międzyoperacyjności generowanej przez źródło. Obok każdego interfejsu ComImportAttribute, który ma , żarówka oferuje opcję konwersji na międzyoperatorykę wygenerowaną przez źródło. Poprawka zmienia interfejs tak, aby używał atrybutu GeneratedComInterfaceAttribute . Obok każdej klasy, która implementuje interfejs z elementem GeneratedComInterfaceAttribute, żarówka oferuje opcję dodania atrybutu GeneratedComClassAttribute do typu. Po przekonwertowaniu typów możesz przenieść DllImport metody, aby użyć metody LibraryImportAttribute.

Ograniczenia

Generator źródła MODELU COM nie obsługuje koligacji apartamentów, używając słowa kluczowego new do aktywowania klasy COClass modelu COM i następujących interfejsów API:

Generator źródła powiązania konfiguracji

Platforma .NET 8 wprowadza generator źródła w celu zapewnienia konfiguracji przyjaznej dla funkcji AOT i przycinania w środowisku ASP.NET Core. Generator jest alternatywą dla istniejącej implementacji opartej na odbiciu.

Sondy generatora źródłowego dla Configure(TOptions)parametrów , Bindi Get wywołuje metodę pobierania informacji o typie. Gdy generator jest włączony w projekcie, kompilator niejawnie wybiera wygenerowane metody w ramach istniejących implementacji struktury opartej na odbiciu.

Do korzystania z generatora nie są wymagane żadne zmiany kodu źródłowego. Jest ona domyślnie włączona w aplikacjach internetowych usługi AOT. W przypadku innych typów projektów generator źródła jest domyślnie wyłączony, ale możesz wyrazić zgodę, ustawiając EnableConfigurationBindingGenerator właściwość na true wartość w pliku projektu:

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

Poniższy kod przedstawia przykład wywoływania powiązania.

public class ConfigBindingSG
{
    static void RunIt(params string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

        // !! Configure call - to be replaced with source-gen'd implementation
        builder.Services.Configure<MyOptions>(section);

        // !! Get call - to be replaced with source-gen'd implementation
        MyOptions? options0 = section.Get<MyOptions>();

        // !! Bind call - to be replaced with source-gen'd implementation
        MyOptions options1 = new();
        section.Bind(options1);

        WebApplication app = builder.Build();
        app.MapGet("/", () => "Hello World!");
        app.Run();
    }

    public class MyOptions
    {
        public int A { get; set; }
        public string S { get; set; }
        public byte[] Data { get; set; }
        public Dictionary<string, string> Values { get; set; }
        public List<MyClass> Values2 { get; set; }
    }

    public class MyClass
    {
        public int SomethingElse { get; set; }
    }
}

Podstawowe biblioteki platformy .NET

Ta sekcja zawiera następujące podtopy:

Odbicie

Wskaźniki funkcji zostały wprowadzone na platformie .NET 5, jednak w tym czasie nie dodano odpowiedniej obsługi odbicia. W przypadku użycia typeof lub odbicia wskaźnika funkcji, na przykład typeof(delegate*<void>()) lub FieldInfo.FieldType odpowiednio, IntPtr zwrócono element . Począwszy od platformy .NET 8, System.Type zamiast tego zwracany jest obiekt. Ten typ zapewnia dostęp do metadanych wskaźnika funkcji, w tym konwencji wywoływania, zwracanego typu i parametrów.

Uwaga

Wystąpienie wskaźnika funkcji, które jest adresem fizycznym funkcji, nadal jest reprezentowane jako IntPtr. Zmienił się tylko typ odbicia.

Nowe funkcje są obecnie implementowane tylko w środowisku uruchomieniowym CoreCLR i MetadataLoadContext.

Dodano nowe interfejsy API do System.Type, takich jak IsFunctionPointer, i do System.Reflection.PropertyInfo, System.Reflection.FieldInfoi System.Reflection.ParameterInfo. Poniższy kod pokazuje, jak używać niektórych nowych interfejsów API do odbicia.

using System;
using System.Reflection;

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

internal class FunctionPointerReflection
{
    public static void RunIt()
    {
        FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

        // Obtain the function pointer type from a field.
        Type? fpType = fieldInfo?.FieldType;

        // New methods to determine if a type is a function pointer.
        Console.WriteLine(
        $"IsFunctionPointer: {fpType?.IsFunctionPointer}");
        Console.WriteLine(
            $"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");

        // New methods to obtain the return and parameter types.
        Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");

        if (fpType is not null)
        {
            foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
            {
                Console.WriteLine($"Parameter type: {parameterType}");
            }
        }

        // Access to custom modifiers and calling conventions requires a "modified type".
        Type? modifiedType = fieldInfo?.GetModifiedFieldType();

        // A modified type forwards most members to its underlying type.
        Type? normalType = modifiedType?.UnderlyingSystemType;

        if (modifiedType is not null)
        {
            // New method to obtain the calling conventions.
            foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
            {
                Console.WriteLine($"Calling convention: {callConv}");
            }
        }

        // New method to obtain the custom modifiers.
        Type[]? modifiers =
            modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();

        if (modifiers is not null)
        {
            foreach (Type modreq in modifiers)
            {
                Console.WriteLine($"Required modifier for first parameter: {modreq}");
            }
        }
    }
}

W poprzednim przykładzie są generowane następujące dane wyjściowe:

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

Serializacja

Wprowadzono wiele ulepszeń System.Text.Json w zakresie serializacji i deserializacji funkcji na platformie .NET 8. Można na przykład dostosować obsługę elementów członkowskich, które nie należą do ładunku JSON.

W poniższych sekcjach opisano inne ulepszenia serializacji:

Aby uzyskać więcej informacji na temat serializacji JSON w ogóle, zobacz Serializacja i deserializacja JSON na platformie .NET.

Wbudowana obsługa dodatkowych typów

Serializator ma wbudowaną obsługę następujących dodatkowych typów.

  • Half, Int128i UInt128 typy liczbowe.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> i ReadOnlyMemory<T> wartości. byte wartości są serializowane do ciągów Base64, a inne typy do tablic JSON.

    JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID"
    JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
    

Generator źródła

Platforma .NET 8 zawiera ulepszenia generatora źródła System.Text.Json, które mają na celu uczynienie natywnego środowiska AOT na równi z serializatorem opartym na odbiciu. Na przykład:

  • Generator źródła obsługuje teraz serializacji typów z właściwościami required i init . Oba te elementy były już obsługiwane w serializacji opartej na odbiciu.

  • Ulepszone formatowanie kodu wygenerowanego przez źródło.

  • JsonSourceGenerationOptionsAttribute parzystość funkcji z elementem JsonSerializerOptions. Aby uzyskać więcej informacji, zobacz Określanie opcji (generowanie źródła).

  • Dodatkowa diagnostyka (na przykład SYSLIB1034 i SYSLIB1039).

  • Nie uwzględniaj typów ignorowanych lub niedostępnych właściwości.

  • Obsługa zagnieżdżania JsonSerializerContext deklaracji w dowolnych rodzajach typów.

  • Obsługa typów generowanych przez kompilator lub niewyraźnych w scenariuszach słabego generowania źródła. Ponieważ typy generowane przez kompilator nie mogą być jawnie określone przez generator źródła, System.Text.Json teraz wykonuje rozpoznawanie najbliższych przodków w czasie wykonywania. Ta rozdzielczość określa najbardziej odpowiedni supertyp, z którym ma być serializować wartość.

  • Nowy typ JsonStringEnumConverter<TEnum>konwertera . Istniejąca JsonStringEnumConverter klasa nie jest obsługiwana w natywnej usłudze AOT. Możesz dodawać adnotacje do typów wyliczenia w następujący sposób:

    [JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
    public enum MyEnum { Value1, Value2, Value3 }
    
    [JsonSerializable(typeof(MyEnum))]
    public partial class MyContext : JsonSerializerContext { }
    

    Aby uzyskać więcej informacji, zobacz Serializowanie pól wyliczenia jako ciągów.

  • Nowa JsonConverter.Type właściwość umożliwia wyszukanie typu wystąpienia innego niż ogólne JsonConverter :

    Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
        => converters.Where(converter => converter.Type != null)
                     .ToDictionary(converter => converter.Type!);
    

    Właściwość jest dopuszczana do wartości null, ponieważ zwraca null wartość dla JsonConverterFactory wystąpień i typeof(T) wystąpień JsonConverter<T> .

Generatory źródeł łańcucha

Klasa JsonSerializerOptions zawiera nową TypeInfoResolverChain właściwość, która uzupełnia istniejącą TypeInfoResolver właściwość. Te właściwości są używane w dostosowywaniu kontraktu do tworzenia łańcuchów generatorów źródeł. Dodanie nowej właściwości oznacza, że nie trzeba określać wszystkich składników łańcuchowych w jednej lokacji wywołań — można je dodać po fakcie. TypeInfoResolverChain Umożliwia również introspekcja łańcucha lub usuwania z niego składników. Aby uzyskać więcej informacji, zobacz Łączenie generatorów źródeł.

Ponadto JsonSerializerOptions.AddContext<TContext>() jest teraz przestarzałe. Został zastąpiony przez TypeInfoResolver właściwości i TypeInfoResolverChain . Aby uzyskać więcej informacji, zobacz SYSLIB0049.

Hierarchie interfejsu

Platforma .NET 8 dodaje obsługę serializacji właściwości z hierarchii interfejsu.

Poniższy kod przedstawia przykład, w którym właściwości zarówno z natychmiast zaimplementowanego interfejsu, jak i jego interfejsu podstawowego są serializowane.

public static void InterfaceHierarchies()
{
    IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
    string json = JsonSerializer.Serialize(value);
    Console.WriteLine(json); // {"Derived":1,"Base":0}
}

public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

Zasady nazewnictwa

JsonNamingPolicy zawiera nowe zasady nazewnictwa dla snake_case (z podkreśleniami) i kebab-case (z łącznikiem) konwersje nazw właściwości. Użyj tych zasad podobnie do istniejących JsonNamingPolicy.CamelCase zasad:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }

Aby uzyskać więcej informacji, zobacz Używanie wbudowanych zasad nazewnictwa.

Właściwości tylko do odczytu

Teraz można wykonać deserializacji na polach lub właściwościach tylko do odczytu (czyli na tych, które nie mają set dostępu).

Aby włączyć tę obsługę globalnie, ustaw nową opcję na PreferredObjectCreationHandlingwartość , na JsonObjectCreationHandling.Populate. Jeśli zgodność jest problemem, można również włączyć funkcjonalność bardziej szczegółową, umieszczając [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] atrybut na określonych typach, których właściwości mają zostać wypełnione, lub na poszczególnych właściwościach.

Rozważmy na przykład następujący kod, który deserializuje się do CustomerInfo typu, który ma dwie właściwości tylko do odczytu.

public static void ReadOnlyProperties()
{
    CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
        { "Names":["John Doe"], "Company":{"Name":"Contoso"} }
        """)!;

    Console.WriteLine(JsonSerializer.Serialize(customer));
}

class CompanyInfo
{
    public required string Name { get; set; }
    public string? PhoneNumber { get; set; }
}

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
    // Both of these properties are read-only.
    public List<string> Names { get; } = new();
    public CompanyInfo Company { get; } = new()
    {
        Name = "N/A",
        PhoneNumber = "N/A"
    };
}

Przed platformą .NET 8 wartości wejściowe zostały zignorowane, a Names właściwości i Company zachowały ich wartości domyślne.

{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}

Teraz wartości wejściowe są używane do wypełniania właściwości tylko do odczytu podczas deserializacji.

{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}

Aby uzyskać więcej informacji o zachowaniu wypełniania deserializacji, zobacz Wypełnianie zainicjowanych właściwości.

Wyłączanie wartości domyślnej opartej na odbiciu

Teraz można wyłączyć używanie serializatora opartego na odbiciu domyślnie. To wyłączenie jest przydatne, aby uniknąć przypadkowego rootingu składników odbicia, które nie są nawet używane, szczególnie w aplikacjach przycinanych i natywnych AOT. Aby wyłączyć domyślną serializacji opartą na odbiciem, wymagając JsonSerializerOptions przekazania argumentu JsonSerializer do metod serializacji i deserializacji, ustaw JsonSerializerIsReflectionEnabledByDefault właściwość MSBuild na false w pliku projektu.

Użyj nowego IsReflectionEnabledByDefault interfejsu API, aby sprawdzić wartość przełącznika funkcji. Jeśli jesteś autorem biblioteki opartym na System.Text.Jsonsystemie , możesz polegać na właściwości , aby skonfigurować wartości domyślne bez przypadkowego zakorzenienia składników odbicia.

Aby uzyskać więcej informacji, zobacz Wyłączanie wartości domyślnych odbicia.

Nowe metody interfejsu API JsonNode

Typy JsonNode i System.Text.Json.Nodes.JsonArray obejmują następujące nowe metody.

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent
    // object, returns its name.
    // Throws InvalidOperationException otherwise.
    public string GetPropertyName();

    // If node is the element of a parent JsonArray,
    // returns its index.
    // Throws InvalidOperationException otherwise.
    public int GetElementIndex();

    // Replaces this instance with a new value,
    // updating the parent object/array accordingly.
    public void ReplaceWith<T>(T value);

    // Asynchronously parses a stream as UTF-8 encoded data
    // representing a single JSON value into a JsonNode.
    public static Task<JsonNode?> ParseAsync(
        Stream utf8Json,
        JsonNodeOptions? nodeOptions = null,
        JsonDocumentOptions documentOptions = default,
        CancellationToken cancellationToken = default);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

Niepublijni członkowie

Możesz zrezygnować z elementów członkowskich innych niż publiczne w umowie serializacji dla danego typu przy użyciu JsonIncludeAttribute adnotacji i JsonConstructorAttribute atrybutów.

public static void NonPublicMembers()
{
    string json = JsonSerializer.Serialize(new MyPoco(42));
    Console.WriteLine(json);
    // {"X":42}

    JsonSerializer.Deserialize<MyPoco>(json);
}

public class MyPoco
{
    [JsonConstructor]
    internal MyPoco(int x) => X = x;

    [JsonInclude]
    internal int X { get; }
}

Aby uzyskać więcej informacji, zobacz Use immutable types and non-public members and accessors (Używanie niezmienialnych typów i elementów członkowskich i metod dostępu innych niż publiczne).

Interfejsy API deserializacji przesyłania strumieniowego

Platforma .NET 8 zawiera nowe IAsyncEnumerable<T> metody rozszerzenia deserializacji przesyłania strumieniowego, na przykład GetFromJsonAsAsyncEnumerable. Podobne metody istniały, które zwracają Task<TResult>, na przykład HttpClientJsonExtensions.GetFromJsonAsync. Nowe metody rozszerzenia wywołują interfejsy API przesyłania strumieniowego i zwracają wartość IAsyncEnumerable<T>.

Poniższy kod pokazuje, jak można użyć nowych metod rozszerzenia.

public async static void StreamingDeserialization()
{
    const string RequestUri = "https://api.contoso.com/books";
    using var client = new HttpClient();
    IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);

    await foreach (Book? book in books)
    {
        Console.WriteLine($"Read book '{book?.title}'");
    }
}

public record Book(int id, string title, string author, int publishedYear);

WithAddedModifier, metoda rozszerzenia

Nowa WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) metoda rozszerzenia umożliwia łatwe wprowadzenie modyfikacji kontraktów serializacji dowolnych IJsonTypeInfoResolver wystąpień.

var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyContext.Default
        .WithAddedModifier(static typeInfo =>
        {
            foreach (JsonPropertyInfo prop in typeInfo.Properties)
            {
                prop.Name = prop.Name.ToUpperInvariant();
            }
        })
};

Nowe przeciążenia JsonContent.Create

Teraz można tworzyć JsonContent wystąpienia przy użyciu kontraktów bezpiecznych przycinania lub generowania źródła. Nowe metody to:

var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);

public record Book(int id, string title, string author, int publishedYear);

[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}

Blokowanie wystąpienia JsonSerializerOptions

Następujące nowe metody umożliwiają kontrolowanie, kiedy JsonSerializerOptions wystąpienie jest zamrożone:

  • JsonSerializerOptions.MakeReadOnly()

    To przeciążenie jest przeznaczone do bezpiecznego przycinania i dlatego zgłasza wyjątek w przypadkach, gdy wystąpienie opcji nie zostało skonfigurowane za pomocą narzędzia rozpoznawania nazw.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Jeśli przejdziesz true do tego przeciążenia, spowoduje to wypełnienie wystąpienia opcji domyślnym rozpoznawaniem odbicia, jeśli go brakuje. Ta metoda jest oznaczona RequiresUnreferenceCode/RequiresDynamicCode i dlatego jest nieodpowiednia dla natywnych aplikacji AOT.

Nowa IsReadOnly właściwość umożliwia sprawdzenie, czy wystąpienie opcji jest zamrożone.

Abstrakcja czasu

Nowa TimeProvider klasa i ITimer interfejs dodają funkcję abstrakcji czasu, dzięki czemu można wyśmiewać czas w scenariuszach testowych. Ponadto można użyć abstrakcji czasu, aby wyśmiewać Task operacje, które polegają na postępie czasu przy użyciu i Task.DelayTask.WaitAsync. Abstrakcja czasu obsługuje następujące niezbędne operacje czasowe:

  • Pobieranie czasu lokalnego i czasu UTC
  • Uzyskiwanie znacznika czasu na potrzeby pomiaru wydajności
  • Tworzenie czasomierza

Poniższy fragment kodu przedstawia kilka przykładów użycia.

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

TimerCallback callback = s => ((State)s!).Signal();

// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
    callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
    private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

Ulepszenia utF8

Jeśli chcesz włączyć zapisywanie reprezentacji typu przypominającej ciąg do zakresu docelowego, zaimplementuj nowy IUtf8SpanFormattable interfejs w swoim typie. Ten nowy interfejs jest ściśle związany z elementem , ale jest przeznaczony dla ISpanFormattableformatu UTF8 i Span<byte> zamiast UTF16 i Span<char>.

IUtf8SpanFormattable został zaimplementowany we wszystkich typach pierwotnych (a także innych), z dokładnie taką samą udostępnioną logiką, niezależnie od tego, czy jest to wartość docelowa string, Span<char>, czy Span<byte>. Ma pełną obsługę wszystkich formatów (w tym nowego specyfikatora binarnego "B") i wszystkich kultur. Oznacza to, że teraz można formatować bezpośrednio do formatu UTF8 z Byte, , DateTimeOffsetUInt32GuidHalfDoubleDecimalDateTimeIPAddressDateOnlyCharComplexIPNetworkInt16SByteNFloatSingleIntPtrRuneInt128TimeOnlyTimeSpanInt64Int32UInt64UInt128UInt16UIntPtri .Version

Nowe Utf8.TryWrite metody zapewniają oparty na protokole UTF8 odpowiednik istniejących MemoryExtensions.TryWrite metod, które są oparte na protokole UTF16. Składnia ciągów interpolowanych umożliwia formatowanie złożonego wyrażenia bezpośrednio w zakresie bajtów UTF8, na przykład:

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

Implementacja rozpoznaje IUtf8SpanFormattable wartości formatu i używa ich implementacji do zapisywania reprezentacji UTF8 bezpośrednio w zakresie docelowym.

Implementacja wykorzystuje również nową Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) metodę, która wraz z jej Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) odpowiednikiem obsługuje kodowanie i dekodowanie w zakresie docelowym. Jeśli zakres nie jest wystarczająco długi, aby pomieścić stan wynikowy, metody zwracają false zamiast zgłaszać wyjątek.

Metody pracy z losowością

Typy System.Random i System.Security.Cryptography.RandomNumberGenerator wprowadzają dwie nowe metody pracy z losowością.

GetItems<T>()

Nowe System.Random.GetItems metody i System.Security.Cryptography.RandomNumberGenerator.GetItems umożliwiają losowe wybieranie określonej liczby elementów z zestawu danych wejściowych. W poniższym przykładzie pokazano, jak używać System.Random.GetItems<T>() (w wystąpieniu Random.Shared dostarczonym przez właściwość), aby losowo wstawić 31 elementów do tablicy. Ten przykład może być używany w grze "Simon", gdzie gracze muszą pamiętać sekwencję kolorowych przycisków.

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

// ...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...

Shuffle<T>()

Nowe Random.Shuffle metody i RandomNumberGenerator.Shuffle<T>(Span<T>) umożliwiają losowe określanie kolejności zakresu. Te metody są przydatne do zmniejszania stronniczość trenowania w uczeniu maszynowym (więc pierwsza rzecz nie zawsze jest trenowana, a ostatnia rzecz zawsze testuje).

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

Typy ukierunkowane na wydajność

Platforma .NET 8 wprowadza kilka nowych typów mających na celu poprawę wydajności aplikacji.

  • System.Collections.Frozen Nowa przestrzeń nazw zawiera typy FrozenDictionary<TKey,TValue> kolekcji i FrozenSet<T>. Te typy nie zezwalają na żadne zmiany kluczy i wartości po utworzeniu kolekcji. To wymaganie umożliwia szybsze operacje odczytu (na przykład TryGetValue()). Te typy są szczególnie przydatne w przypadku kolekcji, które są wypełniane przy pierwszym użyciu, a następnie utrwalane przez czas trwania długotrwałej usługi, na przykład:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Metody takie jak MemoryExtensions.IndexOfAny wyszukiwanie pierwszego wystąpienia dowolnej wartości w przekazanej kolekcji. Nowy System.Buffers.SearchValues<T> typ jest przeznaczony do przekazania do takich metod. W związku z tym platforma .NET 8 dodaje nowe przeciążenia metod, takich jak MemoryExtensions.IndexOfAny akceptowanie wystąpienia nowego typu. Podczas tworzenia wystąpienia SearchValues<T>programu wszystkie dane niezbędne do optymalizacji kolejnych wyszukiwań pochodzą w tym czasie, co oznacza, że praca jest wykonywana z góry.

  • Nowy System.Text.CompositeFormat typ jest przydatny do optymalizacji ciągów formatu, które nie są znane w czasie kompilacji (na przykład jeśli ciąg formatu jest ładowany z pliku zasobu). Trochę dodatkowego czasu spędzimy z góry, aby wykonać pracę, taką jak analizowanie ciągu, ale zapisuje pracę przed wykonaniem każdego użycia.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Nowe System.IO.Hashing.XxHash3 typy i System.IO.Hashing.XxHash128 zapewniają implementacje szybkich algorytmów skrótów XXH3 i XXH128.

System.Numerics i System.Runtime.Intrinsics

W tej sekcji omówiono ulepszenia System.Numerics przestrzeni nazw i System.Runtime.Intrinsics .

  • Vector256<T>, Matrix3x2i Matrix4x4 ulepszono przyspieszanie sprzętowe na platformie .NET 8. Na przykład została ponownie zaimplementowana do Vector256<T> operacji wewnętrznie 2x Vector128<T> , jeśli to możliwe. Pozwala to na częściowe przyspieszenie niektórych funkcji, gdy Vector128.IsHardwareAccelerated == true ale Vector256.IsHardwareAccelerated == false, na przykład w arm64.
  • Funkcje wewnętrzne sprzętu są teraz oznaczone adnotacjami za pomocą atrybutu ConstExpected . Gwarantuje to, że użytkownicy będą świadomi, gdy podstawowy sprzęt oczekuje stałej, a zatem gdy wartość nie stała może nieoczekiwanie zaszkodzić wydajności.
  • Interfejs Lerp(TSelf, TSelf, TSelf)Lerp API został dodany do elementu i w związku z tym do IFloatingPointIeee754<TSelf>float (Single), double (Double) i Half. Ten interfejs API umożliwia efektywne i poprawne wykonywanie interpolacji liniowej między dwiema wartościami.

Vector512 i AVX-512

Rozszerzona obsługa simD dla platformy .NET Core 3.0 w celu uwzględnienia interfejsów API wewnętrznych specyficznych dla platformy dla x86/x64. Dodano obsługę platformy .NET 5 dla architektury Arm64 i .NET 7 dodano międzyplatformowe funkcje wewnętrzne sprzętu. Platforma .NET 8 dodatkowo obsługuje technologię SIMD, wprowadzając Vector512<T> instrukcje intel Advanced Vector Extensions 512 (AVX-512).

W szczególności platforma .NET 8 obejmuje obsługę następujących kluczowych funkcji avX-512:

  • Operacje wektorów 512-bitowych
  • Dodatkowe 16 rejestrów SIMD
  • Dodatkowe instrukcje dostępne dla wektorów 128-bitowych, 256-bitowych i 512-bitowych

Jeśli masz sprzęt, który obsługuje funkcje, teraz Vector512.IsHardwareAccelerated raportuje trueelement .

Platforma .NET 8 dodaje również kilka klas specyficznych dla platformy w System.Runtime.Intrinsics.X86 przestrzeni nazw:

Te klasy są zgodne z tym samym kształtem ogólnym co inne architektury zestawu instrukcji (ISA), które uwidaczniają właściwość i klasę IsSupported zagnieżdżona Avx512F.X64 , aby uzyskać instrukcje dostępne tylko dla procesów 64-bitowych. Ponadto każda klasa ma klasę zagnieżdżoną Avx512F.VL , która uwidacznia Avx512VL rozszerzenia (długość wektora) dla odpowiedniego zestawu instrukcji.

Nawet jeśli nie używasz Vector512jawnie instrukcji specyficznych dla kodu lub Avx512Fspecyficznych dla niego, prawdopodobnie będziesz nadal korzystać z nowej pomocy technicznej avX-512. Dostęp JIT może korzystać z dodatkowych rejestrów i instrukcji niejawnie w przypadku korzystania z funkcji Vector128<T> lub Vector256<T>. Biblioteka klas bazowych używa tych elementów wewnętrznych sprzętu w większości operacji uwidocznionych przez Span<T> interfejsy ReadOnlySpan<T> API matematyczne i w wielu interfejsach API matematycznych udostępnianych dla typów pierwotnych.

Sprawdzanie poprawności danych

System.ComponentModel.DataAnnotations Przestrzeń nazw zawiera nowe atrybuty weryfikacji danych przeznaczone do scenariuszy weryfikacji w usługach natywnych dla chmury. Chociaż istniejące DataAnnotations moduły sprawdzania poprawności są kierowane do typowej weryfikacji wprowadzania danych interfejsu użytkownika, takiej jak pola w formularzu, nowe atrybuty są przeznaczone do weryfikowania danych spoza wprowadzania przez użytkownika, takich jak opcje konfiguracji. Oprócz nowych atrybutów nowe właściwości zostały dodane do RangeAttribute typów i RequiredAttribute .

Nowy interfejs API opis
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Określa, czy ograniczenia są uwzględnione w dozwolonym zakresie.
System.ComponentModel.DataAnnotations.LengthAttribute Określa zarówno dolne, jak i górne granice ciągów lub kolekcji. Na przykład [Length(10, 20)] wymaga co najmniej 10 elementów i co najwyżej 20 elementów w kolekcji.
System.ComponentModel.DataAnnotations.Base64StringAttribute Sprawdza, czy ciąg jest prawidłową reprezentacją Base64.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Określ odpowiednio listy dozwolonych i listy odmowy. Na przykład [AllowedValues("apple", "banana", "mango")].

Metryki

Nowe interfejsy API umożliwiają dołączanie tagów par klucz-wartość do Meter obiektów i podczas Instrument ich tworzenia. Agregatory opublikowanych pomiarów metryk mogą używać tagów do rozróżniania zagregowanych wartości.

var options = new MeterOptions("name")
{
    Version = "version",
    // Attach these tags to the created meter.
    Tags = new TagList()
    {
        { "MeterKey1", "MeterValue1" },
        { "MeterKey2", "MeterValue2" }
    }
};

Meter meter = meterFactory!.Create(options);

Counter<int> counterInstrument = meter.CreateCounter<int>(
    "counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);

Nowe interfejsy API obejmują:

Kryptografia

Platforma .NET 8 dodaje obsługę skrótów SHA-3 elementów pierwotnych. (Algorytm SHA-3 jest obecnie obsługiwany przez system Linux z programem OpenSSL 1.1.1 lub nowszym oraz systemem Windows 11 Build 25324 lub nowszym). Interfejsy API, w których algorytm SHA-2 jest dostępny, oferują teraz komplement SHA-3. Obejmuje SHA3_256to wartości , SHA3_384i SHA3_512 do tworzenia skrótów; HMACSHA3_256, HMACSHA3_384HMACSHA3_512 i dla HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384HashAlgorithmName.SHA3_512 i dla skrótów, w których można skonfigurować algorytm; oraz , RSAEncryptionPadding.OaepSHA3_384i RSAEncryptionPadding.OaepSHA3_256dla RSAEncryptionPadding.OaepSHA3_512 szyfrowania OAEP RSA.

W poniższym przykładzie pokazano, jak używać interfejsów API, w tym SHA3_256.IsSupported właściwości w celu określenia, czy platforma obsługuje algorytm SHA-3.

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

Obsługa algorytmu SHA-3 jest obecnie przeznaczona do obsługi kryptograficznych elementów pierwotnych. Konstrukcje i protokoły wyższego poziomu nie powinny początkowo w pełni obsługiwać algorytmu SHA-3. Te protokoły obejmują certyfikaty X.509, SignedXmli COSE.

Sieć

Obsługa serwera proxy HTTPS

Do tej pory typy serwerów proxy, które HttpClient obsługują wszystkie dozwolone "man-in-the-middle", aby zobaczyć lokację, z którą klient nawiązuje połączenie, nawet w przypadku identyfikatorów URI https. HttpClient teraz obsługuje serwer proxy HTTPS, który tworzy zaszyfrowany kanał między klientem a serwerem proxy, dzięki czemu wszystkie żądania mogą być obsługiwane z pełną prywatnością.

Aby włączyć serwer proxy HTTPS, ustaw zmienną all_proxy środowiskową lub użyj WebProxy klasy , aby programowo kontrolować serwer proxy.

Unix: export all_proxy=https://x.x.x.x:3218 Windows: set all_proxy=https://x.x.x.x:3218

Możesz również użyć WebProxy klasy do programowego sterowania serwerem proxy.

Metody ZipFile oparte na strumieniu

Platforma .NET 8 zawiera nowe przeciążenia ZipFile.CreateFromDirectory , które umożliwiają zbieranie wszystkich plików zawartych w katalogu i spakowanie ich, a następnie zapisanie wynikowego pliku zip w udostępnionym strumieniu. Podobnie nowe ZipFile.ExtractToDirectory przeciążenia umożliwiają udostępnienie strumienia zawierającego spakowany plik i wyodrębnienie jego zawartości do systemu plików. Są to nowe przeciążenia:

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(
        string sourceDirectoryName, Stream destination);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory,
    Encoding? entryNameEncoding);

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, bool overwriteFiles) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

Te nowe interfejsy API mogą być przydatne w przypadku ograniczenia miejsca na dysku, ponieważ unikają konieczności używania dysku jako kroku pośredniego.

Biblioteki rozszerzeń

Ta sekcja zawiera następujące podtopy:

Usługi keyed DI

Usługi wstrzykiwania zależności kluczy (DI) zapewniają środki do rejestrowania i pobierania usług DI przy użyciu kluczy. Za pomocą kluczy można określić zakres sposobu rejestrowania i używania usług. Oto niektóre z nowych interfejsów API:

W poniższym przykładzie pokazano, jak używać usług di z kluczami.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();

class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IServiceProvider serviceProvider)
{
    public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}

public interface ICache
{
    object Get(string key);
}

public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Aby uzyskać więcej informacji, zobacz dotnet/runtime#64427.

Hostowane usługi cyklu życia

Hostowane usługi mają teraz więcej opcji wykonywania podczas cyklu życia aplikacji. IHostedService dostępne StartAsync i StopAsync, a teraz IHostedLifecycleService udostępniają następujące dodatkowe metody:

Te metody są uruchamiane odpowiednio przed i po istniejących punktach.

W poniższym przykładzie pokazano, jak używać nowych interfejsów API.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class HostedLifecycleServices
{
    public async static void RunIt()
    {
        IHostBuilder hostBuilder = new HostBuilder();
        hostBuilder.ConfigureServices(services =>
        {
            services.AddHostedService<MyService>();
        });

        using (IHost host = hostBuilder.Build())
        {
            await host.StartAsync();
        }
    }

    public class MyService : IHostedLifecycleService
    {
        public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
    }
}

Aby uzyskać więcej informacji, zobacz dotnet/runtime#86511.

Walidacja opcji

Generator źródła

Aby zmniejszyć obciążenie uruchamiania i poprawić zestaw funkcji weryfikacji, wprowadziliśmy generator kodu źródłowego, który implementuje logikę walidacji. Poniższy kod przedstawia przykładowe modele i klasy modułu sprawdzania poprawności.

public class FirstModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P1 { get; set; } = string.Empty;

    [Microsoft.Extensions.Options.ValidateObjectMembers(
        typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }
}

public class SecondModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P4 { get; set; } = string.Empty;
}

[OptionsValidator]
public partial class FirstValidatorNoNamespace
    : IValidateOptions<FirstModelNoNamespace>
{
}

[OptionsValidator]
public partial class SecondValidatorNoNamespace
    : IValidateOptions<SecondModelNoNamespace>
{
}

Jeśli aplikacja używa iniekcji zależności, możesz wstrzyknąć walidację, jak pokazano w poniższym przykładowym kodzie.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
    builder.Configuration.GetSection("some string"));

builder.Services.AddSingleton<
    IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
    IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

ValidateOptionsResultBuilder, typ

Platforma .NET 8 wprowadza ValidateOptionsResultBuilder typ ułatwiający tworzenie ValidateOptionsResult obiektu. Co ważne, ten konstruktor umożliwia akumulację wielu błędów. Wcześniej tworzenie obiektu wymaganego ValidateOptionsResult do zaimplementowania IValidateOptions<TOptions>.Validate(String, TOptions) było trudne, a czasami powodowało błędy weryfikacji warstwy. Jeśli wystąpiło wiele błędów, proces weryfikacji często zatrzymywał się przy pierwszym błędzie.

Poniższy fragment kodu przedstawia przykładowe użycie elementu ValidateOptionsResultBuilder.

ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");

// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();

// Reset the builder to allow using it in new validation operation.
builder.Clear();

Konstruktory LoggerMessageAttribute

LoggerMessageAttribute teraz oferuje dodatkowe przeciążenia konstruktora. Wcześniej trzeba było wybrać konstruktor bez parametrów lub konstruktor, który wymagał wszystkich parametrów (identyfikator zdarzenia, poziom dziennika i komunikat). Nowe przeciążenia zapewniają większą elastyczność określania wymaganych parametrów przy użyciu ograniczonego kodu. Jeśli nie podasz identyfikatora zdarzenia, system wygeneruje je automatycznie.

public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);

Metryki rozszerzeń

IMeterFactory, interfejs

Nowy interfejs można zarejestrować IMeterFactory w kontenerach wstrzykiwania zależności (DI) i używać go do tworzenia Meter obiektów w izolowany sposób.

IMeterFactory Zarejestruj kontener di przy użyciu domyślnej implementacji fabryki miernika:

// 'services' is the DI IServiceCollection.
services.AddMetrics();

Użytkownicy mogą następnie uzyskać fabrykę mierników i użyć jej do utworzenia nowego Meter obiektu.

IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();

MeterOptions options = new MeterOptions("MeterName")
{
    Version = "version",
};

Meter meter = meterFactory.Create(options);

MetricCollector T,<klasa T>

Nowa MetricCollector<T> klasa umożliwia rejestrowanie pomiarów metryk wraz ze znacznikami czasu. Ponadto klasa oferuje elastyczność korzystania z wybranego dostawcy czasu na potrzeby dokładnego generowania znacznika czasu.

const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;

var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);

Assert.IsNull(collector.LastMeasurement);

counter.Add(3);

// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);

Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);

System.Numerics.Tensors.TensorPrimitives

Zaktualizowany pakiet NuGet System.Numerics.Tensors zawiera interfejsy API w nowej TensorPrimitives przestrzeni nazw, które dodają obsługę operacji tensorowych. Tensor pierwotnych optymalizuje obciążenia intensywnie korzystające z danych, takie jak sztuczna inteligencja i uczenie maszynowe.

Obciążenia sztucznej inteligencji, takie jak wyszukiwanie semantyczne i pobieranie rozszerzonej generacji (RAG), rozszerzają możliwości języka naturalnego dużych modeli językowych, takich jak ChatGPT, rozszerzając monity o odpowiednie dane. W przypadku tych obciążeń operacje na wektorach — na przykład podobieństwo cosinusu w celu znalezienia najbardziej odpowiednich danych do odpowiedzi na pytanie — mają kluczowe znaczenie. Pakiet System.Numerics.Tensors.TensorPrimitives udostępnia interfejsy API dla operacji wektorowych, co oznacza, że nie trzeba przyjmować zależności zewnętrznej ani pisać własnej implementacji.

Ten pakiet zastępuje pakiet System.Numerics.Tensors.

Aby uzyskać więcej informacji, zobacz wpis w blogu Ogłaszanie platformy .NET 8 RC 2.

Zobacz też