Apa yang baru dalam runtime .NET 8

Artikel ini menjelaskan fitur baru dalam runtime .NET untuk .NET 8.

Peningkatan performa

.NET 8 mencakup peningkatan pada pembuatan kode dan kompilasi just-in time (JIT):

  • Peningkatan performa Arm64
  • Penyempurnaan SIMD
  • Dukungan untuk ekstensi ISA AVX-512 (lihat Vector512 dan AVX-512)
  • Peningkatan cloud-native
  • Peningkatan throughput JIT
  • Perulangan dan pengoptimalan umum
  • Akses yang dioptimalkan untuk bidang yang ditandai dengan ThreadStaticAttribute
  • Alokasi register berturut-turut. Arm64 memiliki dua instruksi untuk pencarian vektor tabel, yang mengharuskan semua entitas dalam operand tuple mereka ada dalam register berturut-turut.
  • JIT/NativeAOT sekarang dapat membuka pendaftaran dan vektorisasi otomatis beberapa operasi memori dengan SIMD, seperti perbandingan, penyalinan, dan nol, jika dapat menentukan ukurannya pada waktu kompilasi.

Selain itu, pengoptimalan yang dipandu profil dinamis (PGO) telah ditingkatkan dan sekarang diaktifkan secara default. Anda tidak perlu lagi menggunakan opsi konfigurasi runtime untuk mengaktifkannya. PGO dinamis bekerja bersama dengan kompilasi berjenjang untuk lebih mengoptimalkan kode berdasarkan instrumentasi tambahan yang diberlakukan selama tingkat 0.

Rata-rata, PGO dinamis meningkatkan performa sekitar 15%. Dalam rangkaian tolok ukur ~ 4600 pengujian, 23% melihat peningkatan performa 20% atau lebih.

Promosi struct codegen

.NET 8 menyertakan pass pengoptimalan promosi fisik baru untuk codegen yang menggeneralisasi kemampuan JIT untuk mempromosikan variabel struktur. Pengoptimalan ini (juga disebut penggantian skalar agregat) menggantikan bidang variabel struktur dengan variabel primitif yang kemudian dapat dialihkan JIT dan mengoptimalkannya dengan lebih tepat.

JIT sudah mendukung pengoptimalan ini tetapi dengan beberapa batasan besar termasuk:

  • Ini hanya didukung untuk struktur dengan empat atau lebih sedikit bidang.
  • Ini hanya didukung jika setiap bidang adalah jenis primitif, atau struktur sederhana yang membungkus jenis primitif.

Promosi fisik menghapus batasan ini, yang memperbaiki sejumlah masalah JIT yang sudah lama ada.

Pengumpulan Sampah

.NET 8 menambahkan kemampuan untuk menyesuaikan batas memori dengan cepat. Ini berguna dalam skenario layanan cloud, di mana permintaan datang dan pergi. Agar hemat biaya, layanan harus meningkatkan dan menurunkan skala konsumsi sumber daya saat permintaan berfluktuasi. Ketika layanan mendeteksi penurunan permintaan, layanan dapat menurunkan skala konsumsi sumber daya dengan mengurangi batas memorinya. Sebelumnya, ini akan gagal karena pengumpul sampah (GC) tidak menyadari perubahan dan mungkin mengalokasikan lebih banyak memori daripada batas baru. Dengan perubahan ini, Anda dapat memanggil RefreshMemoryLimit() API untuk memperbarui GC dengan batas memori baru.

Ada beberapa batasan yang perlu diperhatikan:

  • Pada platform 32-bit (misalnya, Windows x86 dan Linux ARM), .NET tidak dapat menetapkan batas keras tumpukan baru jika belum ada.
  • API mungkin mengembalikan kode status bukan nol yang menunjukkan refresh gagal. Ini dapat terjadi jika penurunan skala terlalu agresif dan tidak meninggalkan ruang bagi GC untuk bermanuver. Dalam hal ini, pertimbangkan untuk memanggil GC.Collect(2, GCCollectionMode.Aggressive) untuk menyusutkan penggunaan memori saat ini, lalu coba lagi.
  • Jika Anda meningkatkan batas memori di luar ukuran yang diyakini GC proses dapat menangani selama startup, RefreshMemoryLimit panggilan akan berhasil, tetapi tidak akan dapat menggunakan lebih banyak memori daripada apa yang dirasakan sebagai batas.

Cuplikan kode berikut menunjukkan cara memanggil API.

GC.RefreshMemoryLimit();

Anda juga dapat me-refresh beberapa pengaturan konfigurasi GC yang terkait dengan batas memori. Cuplikan kode berikut menetapkan batas keras heap ke 100 mebibyte (MiB):

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

API dapat melemparkan InvalidOperationException jika batas keras tidak valid, misalnya, dalam kasus persentase batas keras timbunan negatif dan jika batas keras terlalu rendah. Ini dapat terjadi jika batas keras tumpukan yang akan diatur refresh, baik karena pengaturan AppData baru atau tersirat oleh perubahan batas memori kontainer, lebih rendah dari yang sudah dilakukan.

Globalisasi untuk aplikasi seluler

Aplikasi seluler di iOS, tvOS, dan MacCatalyst dapat memilih mode globalisasi hibrid baru yang menggunakan bundel ICU yang lebih ringan. Dalam mode hibrid, data globalisasi sebagian ditarik dari bundel ICU dan sebagian dari panggilan ke API Asli. Mode hibrid melayani semua lokal yang didukung oleh seluler.

Mode hibrid paling cocok untuk aplikasi yang tidak dapat bekerja dalam mode globalisasi yang invarian dan yang menggunakan budaya yang dipangkas dari data ICU di seluler. Anda juga dapat menggunakannya saat ingin memuat file data ICU yang lebih kecil. (File icudt_hybrid.dat adalah 34,5 % lebih kecil dari file data ICU default icudt.dat.)

Untuk menggunakan mode globalisasi hibrid, atur HybridGlobalization properti MSBuild ke true:

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

Ada beberapa batasan yang perlu diperhatikan:

  • Karena keterbatasan API Asli, tidak semua API globalisasi didukung dalam mode hibrid.
  • Beberapa API yang didukung memiliki perilaku yang berbeda.

Untuk memeriksa apakah aplikasi Anda terpengaruh, lihat Perbedaan perilaku.

Interop COM yang dihasilkan sumber

.NET 8 menyertakan generator sumber baru yang mendukung interoperabilitas dengan antarmuka COM. Anda dapat menggunakan GeneratedComInterfaceAttribute untuk menandai antarmuka sebagai antarmuka COM untuk generator sumber. Generator sumber kemudian akan menghasilkan kode untuk mengaktifkan panggilan dari kode C# ke kode yang tidak dikelola. Ini juga menghasilkan kode untuk mengaktifkan panggilan dari kode yang tidak dikelola ke C#. Generator sumber ini terintegrasi dengan LibraryImportAttribute, dan Anda dapat menggunakan jenis dengan GeneratedComInterfaceAttribute parameter sebagai dan mengembalikan jenis dalam LibraryImportmetode -atribut.

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 sumber juga mendukung atribut baru GeneratedComClassAttribute untuk memungkinkan Anda meneruskan jenis yang menerapkan antarmuka dengan GeneratedComInterfaceAttribute atribut ke kode yang tidak dikelola. Generator sumber akan menghasilkan kode yang diperlukan untuk mengekspos objek COM yang mengimplementasikan antarmuka dan meneruskan panggilan ke implementasi terkelola.

Metode pada antarmuka dengan GeneratedComInterfaceAttribute atribut mendukung semua jenis yang sama dengan LibraryImportAttribute, dan LibraryImportAttribute sekarang mendukung GeneratedComInterfacejenis -atribut dan GeneratedComClassjenis -atribut.

Jika kode C# Anda hanya menggunakan antarmuka -atribut untuk membungkus objek COM dari kode yang tidak dikelola atau membungkus GeneratedComInterfaceobjek terkelola dari C# untuk mengekspos ke kode yang tidak dikelola, Anda dapat menggunakan opsi dalam Options properti untuk menyesuaikan kode mana yang akan dihasilkan. Opsi ini berarti Anda tidak perlu menulis marshaller untuk skenario yang Anda tahu tidak akan digunakan.

Generator sumber menggunakan jenis baru StrategyBasedComWrappers untuk membuat dan mengelola pembungkus objek COM dan pembungkus objek terkelola. Jenis baru ini menangani memberikan pengalaman pengguna .NET yang diharapkan untuk interop COM, sambil menyediakan titik kustomisasi untuk pengguna tingkat lanjut. Jika aplikasi Anda memiliki mekanisme sendiri untuk menentukan jenis dari COM atau jika Anda perlu mendukung skenario yang saat ini tidak didukung COM yang dihasilkan sumber, pertimbangkan untuk menggunakan jenis baru StrategyBasedComWrappers untuk menambahkan fitur yang hilang untuk skenario Anda dan mendapatkan pengalaman pengguna .NET yang sama untuk jenis COM Anda.

Jika Anda menggunakan Visual Studio, penganalisis baru dan perbaikan kode memudahkan untuk mengonversi kode interop COM yang ada untuk menggunakan interop yang dihasilkan sumber. Di samping setiap antarmuka yang memiliki ComImportAttribute, bola lampu menawarkan opsi untuk mengonversi ke interop yang dihasilkan sumber. Perbaikan mengubah antarmuka untuk menggunakan GeneratedComInterfaceAttribute atribut . Dan di samping setiap kelas yang mengimplementasikan antarmuka dengan GeneratedComInterfaceAttribute, bola lampu menawarkan opsi untuk menambahkan GeneratedComClassAttribute atribut ke jenis . Setelah jenis Anda dikonversi, Anda dapat memindahkan metode Anda DllImport untuk menggunakan LibraryImportAttribute.

Batasan

Generator sumber COM tidak mendukung afinitas apartemen, menggunakan new kata kunci untuk mengaktifkan COM CoClass, dan API berikut:

Generator sumber pengikatan konfigurasi

.NET 8 memperkenalkan generator sumber untuk menyediakan konfigurasi AOT dan trim-friendly di ASP.NET Core. Generator adalah alternatif untuk implementasi berbasis refleksi yang sudah ada sebelumnya.

Pemeriksaan generator sumber untuk Configure(TOptions), Bind, dan Get panggilan untuk mengambil info jenis. Ketika generator diaktifkan dalam proyek, kompilator secara implisit memilih metode yang dihasilkan daripada implementasi kerangka kerja berbasis refleksi yang sudah ada sebelumnya.

Tidak ada perubahan kode sumber yang diperlukan untuk menggunakan generator. Ini diaktifkan secara default di aplikasi web AOT. Untuk jenis proyek lain, generator sumber nonaktif secara default, tetapi Anda dapat ikut serta dengan mengatur properti ke EnableConfigurationBindingGeneratortrue dalam file proyek Anda:

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

Kode berikut menunjukkan contoh pemanggilan pengikat.

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

Pustaka .NET Inti

Bagian ini berisi subtopik berikut:

Refleksi

Penunjuk fungsi diperkenalkan di .NET 5, namun, dukungan terkait untuk refleksi tidak ditambahkan pada saat itu. Saat menggunakan typeof atau merefleksikan pada penunjuk fungsi, misalnya, typeof(delegate*<void>()) atau FieldInfo.FieldType masing-masing, ditampilkan IntPtr . Mulai dari .NET 8, System.Type objek dikembalikan sebagai gantinya. Jenis ini menyediakan akses ke metadata penunjuk fungsi, termasuk konvensi panggilan, jenis pengembalian, dan parameter.

Catatan

Instans penunjuk fungsi, yang merupakan alamat fisik ke fungsi, terus diwakili sebagai IntPtr. Hanya jenis pantulan yang telah berubah.

Fungsionalitas baru saat ini hanya diterapkan dalam runtime CoreCLR dan MetadataLoadContext.

API baru telah ditambahkan ke System.Type, seperti IsFunctionPointer, dan ke System.Reflection.PropertyInfo, System.Reflection.FieldInfo, dan System.Reflection.ParameterInfo. Kode berikut menunjukkan cara menggunakan beberapa API baru untuk refleksi.

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

Contoh sebelumnya menghasilkan output berikut:

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

Serialisasi

Banyak peningkatan telah dilakukan pada System.Text.Json fungsionalitas serialisasi dan deserialisasi di .NET 8. Misalnya, Anda dapat menyesuaikan penanganan anggota yang tidak ada di payload JSON.

Bagian berikut ini menjelaskan peningkatan serialisasi lainnya:

Untuk informasi selengkapnya tentang serialisasi JSON secara umum, lihat Serialisasi dan deserialisasi JSON di .NET.

Dukungan bawaan untuk jenis tambahan

Serializer memiliki dukungan bawaan untuk jenis tambahan berikut.

  • Half, Int128, dan UInt128 jenis numerik.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T> dan ReadOnlyMemory<T> nilai. byte nilai diserialisasikan ke string Base64, dan jenis lain ke array JSON.

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

Generator sumber

.NET 8 mencakup penyempurnaan generator sumber System.Text.Json yang bertujuan untuk membuat pengalaman AOT Asli sejalan dengan serializer berbasis refleksi. Misalnya:

  • Generator sumber sekarang mendukung jenis serialisasi dengan required properti dan init . Keduanya sudah didukung dalam serialisasi berbasis refleksi.

  • Peningkatan pemformatan kode yang dihasilkan sumber.

  • JsonSourceGenerationOptionsAttribute paritas fitur dengan JsonSerializerOptions. Untuk informasi selengkapnya, lihat Menentukan opsi (pembuatan sumber).

  • Diagnostik tambahan (seperti SYSLIB1034 dan SYSLIB1039).

  • Jangan sertakan jenis properti yang diabaikan atau tidak dapat diakses.

  • Dukungan untuk deklarasi berlapis JsonSerializerContext dalam jenis jenis sewenang-wenang.

  • Dukungan untuk jenis yang dihasilkan kompilator atau tidak dapat dieksplorasi dalam skenario pembuatan sumber yang ditik lemah. Karena jenis yang dihasilkan kompilator tidak dapat ditentukan secara eksplisit oleh generator sumber, System.Text.Json sekarang melakukan resolusi leluhur terdekat pada waktu proses. Resolusi ini menentukan supertipe yang paling tepat untuk menserialisasikan nilai.

  • Jenis JsonStringEnumConverter<TEnum>pengonversi baru . Kelas yang JsonStringEnumConverter ada tidak didukung di Native AOT. Anda dapat membuat anotasi jenis enum Anda sebagai berikut:

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

    Untuk informasi selengkapnya, lihat Menserialisasikan bidang enum sebagai string.

  • Properti baru JsonConverter.Type memungkinkan Anda mencari jenis instans non-generik JsonConverter :

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

    Properti dapat diubah ke null karena dikembalikan null untuk JsonConverterFactory instans dan typeof(T) untuk JsonConverter<T> instans.

Generator sumber rantai

Kelas JsonSerializerOptions ini mencakup properti baru TypeInfoResolverChain yang melengkapi properti yang TypeInfoResolver ada. Properti ini digunakan dalam penyesuaian kontrak untuk menautkan generator sumber. Penambahan properti baru berarti Anda tidak perlu menentukan semua komponen berantai di satu situs panggilan—mereka dapat ditambahkan setelah fakta. TypeInfoResolverChain juga memungkinkan Anda mengintrospiksi rantai atau menghapus komponen darinya. Untuk informasi selengkapnya, lihat Menggabungkan generator sumber.

Selain itu, JsonSerializerOptions.AddContext<TContext>() sekarang sudah usang. Sudah digantikan oleh TypeInfoResolver properti dan TypeInfoResolverChain . Untuk informasi selengkapnya, lihat SYSLIB0049.

Hierarki antarmuka

.NET 8 menambahkan dukungan untuk menserialisasikan properti dari hierarki antarmuka.

Kode berikut menunjukkan contoh di mana properti dari antarmuka yang segera diimplementasikan dan antarmuka dasarnya diserialisasikan.

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

Kebijakan penamaan

JsonNamingPolicy termasuk kebijakan penamaan baru untuk snake_case (dengan garis bawah) dan kebab-case (dengan tanda hubung) konversi nama properti. Gunakan kebijakan ini mirip dengan kebijakan yang ada JsonNamingPolicy.CamelCase :

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

Untuk informasi selengkapnya, lihat Menggunakan kebijakan penamaan bawaan.

Properti baca-saja

Anda sekarang dapat mendeserialisasi ke bidang atau properti baca-saja (yaitu, yang tidak memiliki set aksesor).

Untuk memilih dukungan ini secara global, atur opsi baru, , PreferredObjectCreationHandlingke JsonObjectCreationHandling.Populate. Jika kompatibilitas menjadi perhatian, Anda juga dapat mengaktifkan fungsionalitas secara lebih terperinci dengan menempatkan [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] atribut pada jenis tertentu yang propertinya akan diisi, atau pada properti individual.

Misalnya, pertimbangkan kode berikut yang mendeserialisasi ke dalam CustomerInfo jenis yang memiliki dua properti baca-saja.

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

Sebelum .NET 8, nilai input diabaikan dan Names properti dan Company mempertahankan nilai defaultnya.

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

Sekarang, nilai input digunakan untuk mengisi properti baca-saja selama deserialisasi.

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

Untuk informasi selengkapnya tentang perilaku deserialisasi isi, lihat Mengisi properti yang diinisialisasi.

Nonaktifkan default berbasis refleksi

Anda sekarang dapat menonaktifkan menggunakan serializer berbasis pantulan secara default. Penonaktifan ini berguna untuk menghindari akar komponen pantulan yang tidak disengaja yang bahkan tidak digunakan, terutama di aplikasi AOT yang dipangkas dan Asli. Untuk menonaktifkan serialisasi berbasis refleksi default dengan mengharuskan JsonSerializerOptions argumen diteruskan ke JsonSerializer metode serialisasi dan deserialisasi, atur JsonSerializerIsReflectionEnabledByDefault properti MSBuild ke false dalam file proyek Anda.

Gunakan API baru IsReflectionEnabledByDefault untuk memeriksa nilai sakelar fitur. Jika Anda adalah pembuat pustaka yang membangun di atas , Anda dapat mengandalkan System.Text.Jsonproperti untuk mengonfigurasi default Anda tanpa komponen refleksi rooting secara tidak sengaja.

Untuk informasi selengkapnya, lihat Menonaktifkan default pantulan.

Metode API JsonNode baru

Jenis JsonNode dan System.Text.Json.Nodes.JsonArray mencakup metode baru berikut.

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

Anggota non-publik

Anda dapat memilih anggota non-publik ke dalam kontrak serialisasi untuk jenis tertentu menggunakan JsonIncludeAttribute dan JsonConstructorAttribute menganotasi atribut.

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

Untuk informasi selengkapnya, lihat Menggunakan jenis yang tidak dapat diubah dan anggota dan aksesor non-publik.

API deserialisasi streaming

.NET 8 menyertakan metode ekstensi deserialisasi streaming baru IAsyncEnumerable<T> , misalnya GetFromJsonAsAsyncEnumerable. Metode serupa telah ada yang mengembalikan Task<TResult>, misalnya, HttpClientJsonExtensions.GetFromJsonAsync. Metode ekstensi baru memanggil API streaming dan mengembalikan IAsyncEnumerable<T>.

Kode berikut menunjukkan bagaimana Anda dapat menggunakan metode ekstensi baru.

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

Metode ekstensi WithAddedModifier

Metode ekstensi baru WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) memungkinkan Anda dengan mudah memperkenalkan modifikasi pada kontrak serialisasi instans arbitrer IJsonTypeInfoResolver .

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

JsonContent Baru.Buat kelebihan beban

Anda sekarang dapat membuat JsonContent instans menggunakan kontrak trim-safe atau yang dihasilkan sumber. Metode baru adalah:

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

Membekukan instans JsonSerializerOptions

Metode baru berikut memungkinkan Anda mengontrol kapan JsonSerializerOptions instans dibekukan:

  • JsonSerializerOptions.MakeReadOnly()

    Kelebihan beban ini dirancang agar aman dipangkas dan oleh karena itu akan memberikan pengecualian jika instans opsi belum dikonfigurasi dengan resolver.

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    Jika Anda meneruskan true ke kelebihan beban ini, ini mengisi instans opsi dengan pemecah masalah pantulan default jika tidak ada. Metode ini ditandai RequiresUnreferenceCode/RequiresDynamicCode dan oleh karena itu tidak cocok untuk aplikasi AOT Asli.

Properti baru IsReadOnly memungkinkan Anda memeriksa apakah instans opsi dibekukan.

Abstraksi waktu

Kelas dan ITimer antarmuka baru TimeProvider menambahkan fungsi abstraksi waktu, yang memungkinkan Anda untuk menipu waktu dalam skenario pengujian. Selain itu, Anda dapat menggunakan abstraksi waktu untuk menipu Task operasi yang mengandalkan perkembangan waktu menggunakan Task.Delay dan Task.WaitAsync. Abstraksi waktu mendukung operasi waktu penting berikut:

  • Mengambil waktu lokal dan UTC
  • Mendapatkan tanda waktu untuk mengukur performa
  • Membuat timer

Cuplikan kode berikut menunjukkan beberapa contoh penggunaan.

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

Peningkatan UTF8

Jika Anda ingin mengaktifkan penulisan representasi seperti string dari jenis Anda ke rentang tujuan, terapkan antarmuka baru IUtf8SpanFormattable pada jenis Anda. Antarmuka baru ini terkait ISpanFormattableerat dengan , tetapi menargetkan UTF8 dan Span<byte> bukan UTF16 dan Span<char>.

IUtf8SpanFormattable telah diimplementasikan pada semua jenis primitif (ditambah yang lain), dengan logika bersama yang sama persis apakah menargetkan string, , Span<char>atau Span<byte>. Ini memiliki dukungan penuh untuk semua format (termasuk penentu biner "B" baru) dan semua budaya. Ini berarti Anda sekarang dapat memformat langsung ke UTF8 dari Byte, , Complex, Char, DateOnly, DateTimeDateTimeOffset, Decimal, GuidInt16TimeOnlyRuneInt128IPNetworkInt32Int64IPAddressIntPtrHalfSByteNFloatTimeSpanDoubleSingle, UInt16, UInt32, UInt64, UInt128, UIntPtr, dan .Version

Metode baru Utf8.TryWrite menyediakan mitra berbasis UTF8 ke metode yang ada MemoryExtensions.TryWrite , yang berbasis UTF16. Anda dapat menggunakan sintaks string terinterpolasi untuk memformat ekspresi kompleks langsung ke dalam rentang byte UTF8, misalnya:

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

Implementasi ini mengenali IUtf8SpanFormattable nilai format dan menggunakan implementasinya untuk menulis representasi UTF8 mereka langsung ke rentang tujuan.

Implementasi ini juga menggunakan metode baru Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) , yang bersama dengan rekannya Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) , mendukung pengodean dan decoding ke dalam rentang tujuan. Jika rentang tidak cukup lama untuk menahan status yang dihasilkan, metode akan kembali false daripada melemparkan pengecualian.

Metode untuk bekerja dengan keacakan

Jenis System.Random dan System.Security.Cryptography.RandomNumberGenerator memperkenalkan dua metode baru untuk bekerja dengan keacakan.

GetItems<T>()

Metode dan System.Security.Cryptography.RandomNumberGenerator.GetItems baru System.Random.GetItems memungkinkan Anda memilih jumlah item tertentu secara acak dari set input. Contoh berikut menunjukkan cara menggunakan System.Random.GetItems<T>() (pada instans yang disediakan oleh Random.Shared properti) untuk menyisipkan 31 item secara acak ke dalam array. Contoh ini dapat digunakan dalam permainan "Simon" di mana pemain harus mengingat urutan tombol berwarna.

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

Acak<T>()

Metode dan RandomNumberGenerator.Shuffle<T>(Span<T>) baru Random.Shuffle memungkinkan Anda mengacak urutan rentang. Metode ini berguna untuk mengurangi bias pelatihan dalam pembelajaran mesin (jadi hal pertama tidak selalu pelatihan, dan hal terakhir selalu diuji).

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

Jenis berfokus pada performa

.NET 8 memperkenalkan beberapa jenis baru yang bertujuan untuk meningkatkan performa aplikasi.

  • Namespace baru System.Collections.Frozen mencakup jenis FrozenDictionary<TKey,TValue> koleksi dan FrozenSet<T>. Jenis ini tidak mengizinkan perubahan apa pun pada kunci dan nilai setelah koleksi dibuat. Persyaratan tersebut memungkinkan operasi baca yang lebih cepat (misalnya, TryGetValue()). Jenis-jenis ini sangat berguna untuk koleksi yang diisi pada penggunaan pertama dan kemudian bertahan selama layanan berumur panjang, misalnya:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Metode seperti MemoryExtensions.IndexOfAny mencari kemunculan pertama dari nilai apa pun dalam koleksi yang dilewatkan. Jenis baru System.Buffers.SearchValues<T> dirancang untuk diteruskan ke metode tersebut. Secara sesuai, .NET 8 menambahkan kelebihan metode baru seperti MemoryExtensions.IndexOfAny itu menerima instans jenis baru. Saat Anda membuat instans SearchValues<T>, semua data yang diperlukan untuk mengoptimalkan pencarian berikutnya diturunkan pada saat itu, yang berarti pekerjaan dilakukan di depan.

  • Jenis baru System.Text.CompositeFormat berguna untuk mengoptimalkan string format yang tidak diketahui pada waktu kompilasi (misalnya, jika string format dimuat dari file sumber daya). Sedikit waktu tambahan dihabiskan di muka untuk melakukan pekerjaan seperti mengurai string, tetapi menghemat pekerjaan agar tidak dilakukan pada setiap penggunaan.

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • Baru System.IO.Hashing.XxHash3 dan System.IO.Hashing.XxHash128 jenis menyediakan implementasi algoritma hash XXH3 dan XXH128 yang cepat.

System.Numerics dan System.Runtime.Intrinsics

Bagian ini mencakup penyempurnaan pada System.Numerics namespace layanan dan System.Runtime.Intrinsics .

  • Vector256<T>, Matrix3x2, dan Matrix4x4 telah meningkatkan akselerasi perangkat keras pada .NET 8. Misalnya, Vector256<T> diimplementasikan kembali untuk secara internal menjadi 2x Vector128<T> operasi, jika memungkinkan. Ini memungkinkan akselerasi parsial dari beberapa fungsi ketika Vector128.IsHardwareAccelerated == true tetapi Vector256.IsHardwareAccelerated == false, seperti pada Arm64.
  • Intrinsik perangkat keras sekarang diannotasikan dengan ConstExpected atribut . Ini memastikan bahwa pengguna menyadari ketika perangkat keras yang mendasar mengharapkan konstanta dan oleh karena itu ketika nilai non-konstan dapat secara tak terduga menyakiti performa.
  • API telah ditambahkan ke IFloatingPointIeee754<TSelf> dan oleh karena itu ke float (Single), double (Double), dan Half.Lerp(TSelf, TSelf, TSelf)Lerp API ini memungkinkan interpolasi linier antara dua nilai dilakukan secara efisien dan benar.

Vektor512 dan AVX-512

.NET Core 3.0 memperluas dukungan SIMD untuk menyertakan API intrinsik perangkat keras khusus platform untuk x86/x64. .NET 5 menambahkan dukungan untuk Arm64 dan .NET 7 menambahkan intrinsik perangkat keras lintas platform. .NET 8 selanjutnya mendukung SIMD dengan memperkenalkan Vector512<T> dan mendukung instruksi Intel Advanced Vector Extensions 512 (AVX-512).

Secara khusus, .NET 8 menyertakan dukungan untuk fitur utama AVX-512 berikut:

  • Operasi vektor 512-bit
  • 16 pendaftaran SIMD tambahan
  • Instruksi tambahan tersedia untuk vektor 128-bit, 256-bit, dan 512-bit

Jika Anda memiliki perangkat keras yang mendukung fungsionalitas, maka Vector512.IsHardwareAccelerated sekarang laporkan true.

.NET 8 juga menambahkan beberapa kelas khusus platform di System.Runtime.Intrinsics.X86 bawah namespace:

Kelas-kelas ini mengikuti bentuk umum yang sama dengan arsitektur set instruksi (ISA) lainnya karena mereka mengekspos IsSupported properti dan kelas berlapis Avx512F.X64 untuk instruksi yang hanya tersedia untuk proses 64-bit. Selain itu, setiap kelas memiliki kelas berlapis Avx512F.VL yang mengekspos Avx512VL ekstensi (panjang vektor) untuk set instruksi yang sesuai.

Bahkan jika Anda tidak secara eksplisit menggunakan Vector512instruksi -spesifik atau Avx512F-spesifik dalam kode Anda, Anda kemungkinan masih akan mendapat manfaat dari dukungan AVX-512 baru. JIT dapat memanfaatkan register dan instruksi tambahan secara implisit saat menggunakan Vector128<T> atau Vector256<T>. Pustaka kelas dasar menggunakan intrinsik perangkat keras ini secara internal dalam sebagian besar operasi yang diekspos oleh Span<T> dan ReadOnlySpan<T> dan di banyak API matematika yang diekspos untuk jenis primitif.

Validasi Data

Namespace System.ComponentModel.DataAnnotations menyertakan atribut validasi data baru yang ditujukan untuk skenario validasi di layanan cloud-native. Meskipun validator yang sudah ada DataAnnotations sebelumnya diarahkan ke validasi entri data UI yang khas, seperti bidang pada formulir, atribut baru dirancang untuk memvalidasi data non-entri pengguna, seperti opsi konfigurasi. Selain atribut baru, properti baru ditambahkan ke RangeAttribute jenis dan RequiredAttribute .

API Baru Deskripsi
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
Menentukan apakah batas disertakan dalam rentang yang diizinkan.
System.ComponentModel.DataAnnotations.LengthAttribute Menentukan batas bawah dan atas untuk string atau koleksi. Misalnya, [Length(10, 20)] membutuhkan setidaknya 10 elemen dan paling banyak 20 elemen dalam sebuah koleksi.
System.ComponentModel.DataAnnotations.Base64StringAttribute Memvalidasi bahwa string adalah representasi Base64 yang valid.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Tentukan daftar izinkan dan daftar tolak. Contohnya, [AllowedValues("apple", "banana", "mango")].

Metrik

API baru memungkinkan Anda melampirkan tag pasangan kunci-nilai ke Meter objek dan Instrument saat Anda membuatnya. Agregator pengukuran metrik yang diterbitkan dapat menggunakan tag untuk membedakan nilai agregat.

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

API baru meliputi:

Kriptografi

.NET 8 menambahkan dukungan untuk primitif hash SHA-3. (SHA-3 saat ini didukung oleh Linux dengan OpenSSL 1.1.1 atau yang lebih baru dan Windows 11 Build 25324 atau yang lebih baru.) API di mana SHA-2 sekarang tersedia menawarkan pujian SHA-3. Ini termasuk SHA3_256, SHA3_384, dan SHA3_512 untuk hashing; HMACSHA3_256, , HMACSHA3_384dan HMACSHA3_512 untuk HMAC; HashAlgorithmName.SHA3_256, , HashAlgorithmName.SHA3_384dan HashAlgorithmName.SHA3_512 untuk hashing di mana algoritma dapat dikonfigurasi; dan RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384, dan RSAEncryptionPadding.OaepSHA3_512 untuk enkripsi RSA OAEP.

Contoh berikut menunjukkan cara menggunakan API, termasuk SHA3_256.IsSupported properti untuk menentukan apakah platform mendukung 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
{
    // ...
}

Dukungan SHA-3 saat ini ditujukan untuk mendukung primitif kriptografi. Konstruksi dan protokol tingkat yang lebih tinggi tidak diharapkan untuk sepenuhnya mendukung SHA-3 pada awalnya. Protokol ini termasuk sertifikat X.509, SignedXml, dan COSE.

Jaringan

Dukungan untuk proksi HTTPS

Hingga saat ini, jenis proksi yang HttpClient mendukung semua mengizinkan "man-in-the-middle" untuk melihat situs mana yang disambungkan klien, bahkan untuk URI HTTPS. HttpClient sekarang mendukung proksi HTTPS, yang membuat saluran terenkripsi antara klien dan proksi sehingga semua permintaan dapat ditangani dengan privasi penuh.

Untuk mengaktifkan proksi HTTPS, atur all_proxy variabel lingkungan, atau gunakan WebProxy kelas untuk mengontrol proksi secara terprogram.

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

Anda juga dapat menggunakan WebProxy kelas untuk mengontrol proksi secara terprogram.

Metode ZipFile berbasis aliran

.NET 8 menyertakan kelebihan beban ZipFile.CreateFromDirectory baru yang memungkinkan Anda mengumpulkan semua file yang disertakan dalam direktori dan zip, lalu menyimpan file zip yang dihasilkan ke aliran yang disediakan. Demikian pula, kelebihan beban baru ZipFile.ExtractToDirectory memungkinkan Anda menyediakan aliran yang berisi file zip dan mengekstrak kontennya ke dalam sistem file. Ini adalah kelebihan beban baru:

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

API baru ini dapat berguna ketika ruang disk dibatasi, karena mereka menghindari harus menggunakan disk sebagai langkah perantara.

Pustaka ekstensi

Bagian ini berisi subtopik berikut:

Layanan DI utama

Layanan injeksi dependensi utama (DI) menyediakan sarana untuk mendaftar dan mengambil layanan DI menggunakan kunci. Dengan menggunakan kunci, Anda dapat mencakup bagaimana Anda mendaftar dan mengonsumsi layanan. Berikut adalah beberapa API baru:

Contoh berikut menunjukkan kepada Anda cara menggunakan layanan DI kunci.

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

Untuk informasi selengkapnya, lihat dotnet/runtime#64427.

Layanan siklus hidup yang dihosting

Layanan yang dihosting sekarang memiliki lebih banyak opsi untuk eksekusi selama siklus hidup aplikasi. IHostedService disediakan StartAsync dan StopAsync, dan sekarang IHostedLifecycleService menyediakan metode tambahan ini:

Metode ini masing-masing berjalan sebelum dan sesudah titik yang ada.

Contoh berikut menunjukkan cara menggunakan API baru.

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

Untuk informasi selengkapnya, lihat dotnet/runtime#86511.

Validasi opsi

Generator sumber

Untuk mengurangi overhead startup dan meningkatkan set fitur validasi, kami telah memperkenalkan generator kode sumber yang mengimplementasikan logika validasi. Kode berikut menunjukkan contoh model dan kelas validator.

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

Jika aplikasi Anda menggunakan injeksi dependensi, Anda dapat menyuntikkan validasi seperti yang ditunjukkan dalam kode contoh berikut.

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

Jenis ValidateOptionsResultBuilder

.NET 8 memperkenalkan ValidateOptionsResultBuilder jenis untuk memfasilitasi pembuatan ValidateOptionsResult objek. Yang penting, penyusun ini memungkinkan akumulasi beberapa kesalahan. Sebelumnya, membuat ValidateOptionsResult objek yang diperlukan untuk diterapkan IValidateOptions<TOptions>.Validate(String, TOptions) sulit dan terkadang mengakibatkan kesalahan validasi berlapis. Jika ada beberapa kesalahan, proses validasi sering dihentikan pada kesalahan pertama.

Cuplikan kode berikut menunjukkan contoh penggunaan 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();

Konstruktor LoggerMessageAttribute

LoggerMessageAttribute sekarang menawarkan kelebihan beban konstruktor tambahan. Sebelumnya, Anda harus memilih konstruktor tanpa parameter atau konstruktor yang memerlukan semua parameter (ID peristiwa, tingkat log, dan pesan). Kelebihan beban baru menawarkan fleksibilitas yang lebih besar dalam menentukan parameter yang diperlukan dengan kode yang dikurangi. Jika Anda tidak memberikan ID peristiwa, sistem akan membuatnya secara otomatis.

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

Metrik ekstensi

Antarmuka IMeterFactory

Anda dapat mendaftarkan antarmuka baru IMeterFactory dalam kontainer injeksi dependensi (DI) dan menggunakannya untuk membuat Meter objek dengan cara yang terisolasi.

Daftarkan IMeterFactory ke kontainer DI menggunakan implementasi pabrik meter default:

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

Konsumen kemudian dapat memperoleh pabrik meteran dan menggunakannya untuk membuat objek baru Meter .

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

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

Meter meter = meterFactory.Create(options);

Kelas T MetricCollector<>

Kelas baru MetricCollector<T> memungkinkan Anda merekam pengukuran metrik bersama dengan tanda waktu. Selain itu, kelas ini menawarkan fleksibilitas untuk menggunakan penyedia waktu pilihan Anda untuk pembuatan tanda waktu yang akurat.

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

Paket NuGet System.Numerics.Tensors yang diperbarui menyertakan API di namespace baru TensorPrimitives yang menambahkan dukungan untuk operasi tensor. Primitif tensor mengoptimalkan beban kerja intensif data seperti AI dan pembelajaran mesin.

Beban kerja AI seperti pencarian semantik dan retrieval-augmented generation (RAG) memperluas kemampuan bahasa alami model bahasa besar seperti ChatGPT dengan menambah permintaan dengan data yang relevan. Untuk beban kerja ini, operasi pada vektor—seperti kesamaan kosinus untuk menemukan data yang paling relevan untuk menjawab pertanyaan—sangat penting. Paket System.Numerics.Tensors.TensorPrimitives menyediakan API untuk operasi vektor, yang berarti Anda tidak perlu mengambil dependensi eksternal atau menulis implementasi Anda sendiri.

Paket ini menggantikan paket System.Numerics.Tensors.

Untuk informasi selengkapnya, lihat posting blog Mengumumkan .NET 8 RC 2.

Lihat juga