Bagikan melalui


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 teknologi 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 tabel vektor, yang memerlukan semua entitas dalam operand tuple mereka ada dalam register-register yang berurutan.
  • JIT/NativeAOT sekarang dapat menguroll dan melakukan vektorisasi otomatis pada beberapa operasi memori dengan SIMD, seperti perbandingan, penyalinan, dan pengosongan, jika ukurannya dapat ditentukan saat 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 sama dengan kompilasi berjenjang untuk lebih mengoptimalkan kode berdasarkan instrumentasi tambahan yang diterapkan selama level 0.

Rata-rata, PGO dinamis meningkatkan kinerja sekitar 15%. Dalam suite pengujian ~4600 tes, 23% mengalami peningkatan performa 20% atau lebih.

Pengoptimalan struct codegen

.NET 8 menyertakan langkah optimasi promosi fisik baru untuk codegen yang menggeneralisasi kemampuan JIT dalam mempromosikan variabel struktur. Pengoptimalan ini (saja disebut penggantian skalar agregat) menggantikan field dari variabel struct dengan variabel primitif yang dapat dipahami dan dioptimalkan lebih tepat oleh JIT.

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 API RefreshMemoryLimit() 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 menurut GC dapat ditangani proses selama startup, panggilan RefreshMemoryLimit 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 maksimum heap sebesar 100 mebibyte (MiB):

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

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

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 diambil dari bundel ICU dan sebagian dari panggilan ke API Native. Mode hibrid melayani semua lokal yang didukung olehseluler.

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 properti HybridGlobalization 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 oleh 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 yang menggunakan GeneratedComInterfaceAttribute sebagai parameter dan jenis pengembalian dalam metode beratribut LibraryImport.

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 GeneratedComClassAttribute baru untuk memungkinkan Anda meneruskan jenis yang menerapkan antarmuka dengan atribut GeneratedComInterfaceAttribute 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 atribut GeneratedComInterfaceAttribute mendukung semua jenis yang sama seperti LibraryImportAttribute, dan LibraryImportAttribute sekarang mendukung jenis yang memiliki atribut GeneratedComInterfacedan atribut GeneratedComClass.

Jika kode C# Anda hanya menggunakan antarmuka GeneratedComInterface-attributed untuk membungkus objek COM dari kode yang tidak dikelola atau membungkus objek terkelola dari C# untuk mengekspos ke kode yang tidak dikelola, Anda dapat menggunakan opsi di properti Options 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 StrategyBasedComWrappers baru untuk membuat dan mengelola pembungkus objek COM dan pembungkus objek terkelola. Jenis baru ini berfungsi untuk memberikan pengalaman pengguna .NET yang diharapkan untuk interop COM, sambil menyediakan titik kustomisasi bagi pengguna yang lebih mahir. 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 StrategyBasedComWrappers baru untuk menambahkan fitur yang hilang untuk skenario Anda dan dapatkan 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 atribut GeneratedComInterfaceAttribute. Di sebelah setiap kelas yang mengimplementasikan antarmuka dengan GeneratedComInterfaceAttribute, bola lampu menawarkan opsi untuk menambahkan atribut GeneratedComClassAttribute ke tipe. Setelah tipe Anda dikonversi, Anda dapat mengubah metode DllImport Anda agar menggunakan LibraryImportAttribute.

Keterbatasan

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

Generator sumber pengikatan konfigurasi

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

Generator sumber menyelidiki panggilan Configure(TOptions), Bind, dan Get untuk mengambil informasi 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 bawaan di aplikasi web yang dikompilasi AOT, dan ketika PublishTrimmed diatur menjadi true (aplikasi .NET 8+). Untuk jenis proyek lain, generator sumber nonaktif secara default, tetapi Anda dapat ikut serta dengan mengatur properti EnableConfigurationBindingGenerator ke true dalam file proyek Anda:

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

Kode berikut ini menunjukkan contoh pemanggilan binder.

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 Core

Bagian ini berisi subtopik berikut:

Refleksi

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

Nota

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

Fungsionalitas serialisasi dan deserialisasi System.Text.Json telah mengalami banyak peningkatan di .NET 8. Misalnya, Anda dapat menyesuaikan penanganan properti JSON yang tidak ada diPOCO .

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.

  • jenis numerik Half, Int128, dan UInt128.

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Nilai Memory<T> dan ReadOnlyMemory<T>. byte nilai diserialisasikan ke string Base64, dan jenis-jenis lainnya dalam bentuk 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 properti required dan init. Keduanya sudah didukung dalam serialisasi berbasis refleksi.

  • Peningkatan pemformatan kode yang dihasilkan sumber.

  • JsonSourceGenerationOptionsAttribute memiliki kesetaraan 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 penempatan bertingkat deklarasi JsonSerializerContext dalam jenis sembarang.

  • Dukungan untuk jenis yang dihasilkan kompilator atau jenis tak terucapkan dalam skenario generasi sumber berpengetikan lemah. Karena tipe yang dihasilkan oleh kompilator tidak dapat ditentukan secara eksplisit oleh generator kode sumber, System.Text.Json sekarang melakukan resolusi pendahulu terdekat pada waktu eksekusi. Resolusi ini menentukan supertipe yang paling tepat untuk menserialisasikan nilai.

  • Jenis pengonversi baru JsonStringEnumConverter<TEnum>. Kelas JsonStringEnumConverter yang 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 Serialisasi bidang enum sebagai string.

  • Properti JsonConverter.Type baru memungkinkan Anda mengetahui tipe dari instans JsonConverter yang non-generik.

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

    Properti bisa bernilai null karena menampilkan null untuk instans JsonConverterFactory dan typeof(T) untuk instans JsonConverter<T>.

Generator sumber rantai

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

Selain itu, JsonSerializerOptions.AddContext<TContext>() sekarang usang. Ini telah digantikan oleh properti TypeInfoResolver 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 menyertakan kebijakan penamaan baru untuk konversi nama properti snake_case (dengan garis bawah) dan kebab-case (dengan tanda hubung). Gunakan kebijakan ini mirip dengan kebijakan JsonNamingPolicy.CamelCase yang ada:

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

Untuk informasi selengkapnya, lihat Gunakan kebijakan penamaan bawaan.

Properti hanya bisa dibaca

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

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

Misalnya, pertimbangkan kode berikut yang mendeserialisasi ke dalam jenis CustomerInfo 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 properti Names 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 mengisi perilaku deserialisasi, lihat Mengisi properti yang diinisialisasi.

Nonaktifkan pengaturan standar berbasis refleksi

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

Gunakan API IsReflectionEnabledByDefault baru untuk memeriksa nilai sakelar fitur. Jika Anda adalah pengembang pustaka yang mengembangkan menggunakan System.Text.Json, Anda dapat mengandalkan properti untuk menyetel pengaturan default Anda tanpa secara tidak sengaja menetapkan komponen refleksi sebagai akar.

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 tidak publik

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

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 Gunakan jenis yang tidak dapat diubah dan anggota non-publik serta pengaksesnya.

API deserialisasi streaming

.NET 8 menyertakan metode ekstensi deserialisasi streaming IAsyncEnumerable<T> baru, 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 WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) baru memungkinkan Anda dengan mudah memperkenalkan modifikasi pada kontrak serialisasi instans IJsonTypeInfoResolver apa saja.

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 instans JsonContent menggunakan kontrak trim-safe atau kontrak yang dihasilkan dari 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 sebuah instans JsonSerializerOptions

Metode baru berikut memungkinkan Anda mengontrol kapan instans JsonSerializerOptions 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 melewati true ke overload ini, instans opsi akan diisi dengan penyelesai refleksi default jika penyelesai tidak ada. Metode ini ditandai RequiresUnreferenceCode/RequiresDynamicCode dan oleh karena itu tidak cocok untuk aplikasi AOT Asli.

Properti IsReadOnly baru memungkinkan Anda memeriksa apakah instans opsi dibekukan.

Abstraksi waktu

Kelas TimeProvider baru dan antarmuka ITimer menambahkan fungsionalitas abstraksi waktu , yang memungkinkan Anda untuk memanipulasi waktu dalam skenario pengujian. Selain itu, Anda dapat menggunakan abstraksi waktu untuk mensimulasikan operasi Task 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 memungkinkan pencetakan representasi mirip string dari jenis Anda ke dalam rentang tujuan, terapkan antarmuka IUtf8SpanFormattable yang baru pada jenis Anda. Antarmuka baru ini terkait erat dengan ISpanFormattable, tetapi menargetkan UTF8 dan Span<byte> alih-alih UTF16 dan Span<char>.

IUtf8SpanFormattable telah diimplementasikan pada semua jenis primitif (dan lainnya), dengan logika bersama yang persis sama 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, DateTime, DateTimeOffset, Decimal, Double, Guid, Half, IPAddress, IPNetwork, Int16, Int32, Int64, Int128, IntPtr, NFloat, SByte, Single, Rune, TimeOnly, TimeSpan, UInt16, UInt32, UInt64, UInt128, UIntPtr, dan Version.

Metode Utf8.TryWrite baru menyediakan mitra berbasis UTF8 ke metode MemoryExtensions.TryWrite yang ada, 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 mengenali IUtf8SpanFormattable pada nilai format dan menggunakan implementasinya untuk menulis representasi UTF8 mereka langsung ke rentang tujuan.

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

Metode untuk menangani keacakan

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

GetItems<T>()

Metode System.Random.GetItems dan System.Security.Cryptography.RandomNumberGenerator.GetItems baru memungkinkan Anda memilih jumlah item yang ditentukan secara acak dari set input. Contoh berikut menunjukkan cara menggunakan System.Random.GetItems<T>() (pada instans yang disediakan oleh properti Random.Shared) 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 ...

Mengacak<T>()

Fungsi Random.Shuffle dan RandomNumberGenerator.Shuffle<T>(Span<T>) yang baru memungkinkan Anda mengacak urutan elemen dalam 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 System.Collections.Frozen baru mencakup jenis koleksi FrozenDictionary<TKey,TValue> 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();
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • Metode seperti MemoryExtensions.IndexOfAny mencari kemunculan pertama nilai apa pun dalam koleksi yang dilewatkan. Jenis System.Buffers.SearchValues<T> baru dirancang untuk diteruskan ke metode tersebut. Sejalan dengan itu, .NET 8 menambahkan overload baru dari metode seperti MemoryExtensions.IndexOfAny yang menerima instans dari tipe baru. Saat Anda membuat instans SearchValues<T>, semua data yang diperlukan untuk mengoptimalkan pencarian berikutnya diperoleh pada saat itu, yang berarti pekerjaan dilakukan sebelumnya.

  • Jenis System.Text.CompositeFormat baru 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 awal untuk melakukan pekerjaan seperti mengurai string, tetapi hal ini menghemat waktu karena pekerjaan tersebut tidak perlu dilakukan setiap kali 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);
    
  • Jenis System.IO.Hashing.XxHash3 dan System.IO.Hashing.XxHash128 baru menyediakan implementasi algoritma hash XXH3 dan XXH128 yang cepat.

System.Numerics dan System.Runtime.Intrinsics

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

  • Vector256<T>, Matrix3x2, dan Matrix4x4 telah meningkatkan akselerasi perangkat keras pada .NET 8. Misalnya, Vector256<T> diimplementasikan ulang untuk secara internal menjadi operasi 2x Vector128<T>, jika memungkinkan. Ini memungkinkan akselerasi parsial dari beberapa fungsi ketika Vector128.IsHardwareAccelerated == true tetapi Vector256.IsHardwareAccelerated == false, seperti pada Arm64.
  • Intrinsik perangkat keras sekarang diberi anotasi dengan atribut ConstExpected. 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 Lerp(TSelf, TSelf, TSelf)Lerp telah ditambahkan ke IFloatingPointIeee754<TSelf> dan oleh karena itu ke float (Single), double (Double), dan Half. 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 memperkenalkan dukungan untuk Arm64 dan .NET 7 menambahkan fitur intrinsik perangkat keras lintas platform. .NET 8 selanjutnya mendukung SIMD dengan memperkenalkan Vector512<T> dan dukungan untuk 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 fungsi tersebut, Vector512.IsHardwareAccelerated sekarang melaporkan true.

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

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

Bahkan jika Anda tidak secara eksplisit menggunakan instruksi spesifik Vector512atau Avx512Fdalam kode Anda, Anda kemungkinan masih akan mendapat manfaat dari dukungan AVX-512 yang 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 DataAnnotations yang sudah ada 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 jenis RangeAttribute.

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 koleksi.
System.ComponentModel.DataAnnotations.Base64StringAttribute Memvalidasi bahwa string adalah representasi Base64 yang valid.
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
Tentukan daftar yang diizinkan dan daftar yang ditolak, masing-masing. Misalnya, [AllowedValues("apple", "banana", "mango")].

Metrik

API baru memungkinkan Anda melampirkan tag pasangan nilai kunci ke objek Meter 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 peng-hash-an 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-API yang menyediakan SHA-2 sekarang menawarkan pelengkap SHA-3. Ini termasuk SHA3_256, SHA3_384, dan SHA3_512 untuk hashing; HMACSHA3_256, HMACSHA3_384, dan HMACSHA3_512 untuk HMAC; HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384, dan 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 properti SHA3_256.IsSupported 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, semua jenis proksi yang didukung oleh HttpClient memungkinkan seorang "man-in-the-middle" untuk melihat situs mana yang dihubungkan oleh 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 variabel lingkungan all_proxy, atau gunakan kelas WebProxy 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 kelas WebProxy untuk mengontrol proksi secara terprogram.

Metode ZipFile berbasis aliran

.NET 8 menyertakan overload baru dari ZipFile.CreateFromDirectory yang memungkinkan Anda mengumpulkan semua file yang disertakan dalam direktori dan mengompres mereka menjadi zip, lalu menyimpan file zip yang dihasilkan ke dalam stream yang disediakan. Demikian pula, fitur overload ZipFile.ExtractToDirectory baru memungkinkan Anda menyediakan stream yang berisi file zip dan mengekstrak kontennya di 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 dengan kunci (DI) menyediakan sarana untuk mendaftar dan mengambil layanan DI menggunakan kunci. Dengan menggunakan kunci, Anda dapat mengatur lingkup cara Anda mendaftarkan dan menggunakan 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 menyediakan StartAsync dan StopAsync, dan sekarang IHostedLifecycleService menyediakan metode tambahan ini:

Metode-metode ini dijalankan sebelum dan sesudah titik-titik yang ada masing-masing.

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

Tipe ValidateOptionsResultBuilder

.NET 8 memperkenalkan jenis ValidateOptionsResultBuilder untuk memfasilitasi pembuatan objek ValidateOptionsResult. Yang penting, pembangun ini memungkinkan akumulasi banyak kesalahan. Sebelumnya, membuat objek ValidateOptionsResult yang diperlukan untuk menerapkan 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 dari 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 IMeterFactory baru dalam kontainer injeksi dependensi (DI) dan menggunakannya untuk membuat objek Meter dengan cara yang terisolasi.

Daftarkan IMeterFactory ke kontainer DI menggunakan implementasi pabrik meteran default:

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

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

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

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

Meter meter = meterFactory.Create(options);

Kelas MetricCollector<T>

Kelas MetricCollector<T> baru 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 System.Numerics.Tensors NuGet yang diperbarui menyertakan API dalam jenis System.Numerics.Tensors.TensorPrimitives baru 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. Jenis TensorPrimitives menyediakan API untuk operasi vektor.

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

Dukungan AOT bawaan

Opsi untuk mempublikasikan sebagai Native AOT pertama kali diperkenalkan di .NET 7. Menerbitkan aplikasi dengan Native AOT membuat versi aplikasi yang sepenuhnya mandiri yang tidak memerlukan runtime—semuanya disertakan dalam satu file. .NET 8 membawa peningkatan berikut pada publikasi Native AOT:

  • Menambahkan dukungan untuk arsitektur x64 dan Arm64 pada macOS.

  • Mengurangi ukuran aplikasi AOT Asli di Linux hingga 50%. Tabel berikut menunjukkan ukuran aplikasi "Halo Dunia" yang diterbitkan dengan Native AOT yang menyertakan seluruh runtime .NET pada .NET 7 vs. .NET 8:

    Sistem operasi .NET 7 .NET 8
    Linux x64 (dengan -p:StripSymbols=true) 3,76 MB 1,84 MB
    Windows x64 2,85 MB 1,77 MB
  • Memungkinkan Anda menentukan preferensi pengoptimalan: ukuran atau kecepatan. Secara default, pengkompilasi memilih untuk menghasilkan kode cepat sambil memperhatikan ukuran aplikasi. Namun, Anda dapat menggunakan properti <OptimizationPreference> MSBuild untuk mengoptimalkan khusus untuk satu atau yang lain. Untuk informasi selengkapnya, lihat Optimalkan penyebaran AOT.

Menargetkan platform seperti iOS dengan Native AOT

.NET 8 memulai pekerjaan untuk mengaktifkan dukungan AOT Asli untuk platform seperti iOS. Anda sekarang dapat membangun dan menjalankan aplikasi .NET iOS dan .NET MAUI dengan Native AOT pada platform berikut:

  • ios
  • iossimulator
  • maccatalyst
  • tvos
  • tvossimulator

Pengujian awal menunjukkan bahwa ukuran aplikasi pada disk berkurang sekitar 35% untuk aplikasi .NET iOS yang menggunakan AOT Asli alih-alih Mono. Ukuran aplikasi pada disk untuk aplikasi .NET MAUI iOS berkurang hingga 50%. Selain itu, waktu startup juga lebih cepat. Aplikasi .NET iOS memiliki sekitar 28% waktu startup yang lebih cepat, sementara aplikasi .NET MAUI iOS memiliki sekitar 50% performa startup yang lebih baik dibandingkan dengan Mono. Dukungan .NET 8 bersifat eksperimental dan hanya langkah pertama untuk fitur secara keseluruhan. Untuk informasi selengkapnya, lihat posting blog .NET MAUI tentang Peningkatan Performa .NET 8 .

Dukungan AOT asli tersedia sebagai fitur keikutsertaan yang ditujukan untuk penyebaran aplikasi; Mono masih merupakan runtime default untuk pengembangan dan penyebaran aplikasi. Untuk membangun dan menjalankan aplikasi .NET MAUI dengan Native AOT pada perangkat iOS, gunakan dotnet workload install maui untuk menginstal beban kerja .NET MAUI dan dotnet new maui -n HelloMaui untuk membuat aplikasi. Kemudian, atur properti MSBuild PublishAot ke true dalam file proyek.

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

Saat Anda mengatur properti yang diperlukan dan menjalankan dotnet publish seperti ditunjukkan pada contoh berikut, aplikasi akan disebarkan dengan menggunakan Native AOT.

dotnet publish -f net8.0-ios -c Release -r ios-arm64  /t:Run

Keterbatasan

Tidak semua fitur iOS kompatibel dengan Native AOT. Demikian pula, tidak semua pustaka yang umum digunakan di iOS kompatibel dengan NativeAOT. Selain batasan yang ada dari penyebaran Asli AOT, daftar berikut menunjukkan beberapa batasan lain saat menargetkan platform mirip iOS:

  • Penggunaan Native AOT hanya diaktifkan selama penyebaran aplikasi (dotnet publish).
  • Debugging kode terkelola hanya didukung dengan Mono.
  • Kompatibilitas dengan kerangka kerja .NET MAUI terbatas.

Kompilasi AOT untuk aplikasi Android

Untuk mengurangi ukuran aplikasi, aplikasi .NET dan .NET MAUI yang menargetkan Android menggunakan memprofil mode kompilasi AOT (ahead-of-time) saat dibangun dalam mode Rilis. Kompilasi AOT yang difilter memengaruhi lebih sedikit metode daripada kompilasi AOT reguler. .NET 8 memperkenalkan properti <AndroidStripILAfterAOT> yang memungkinkan Anda memilih kompilasi AOT lebih lanjut untuk aplikasi Android guna mengurangi ukuran aplikasi lebih banyak lagi.

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>

Secara default, mengatur AndroidStripILAfterAOT ke true menggantikan pengaturan default AndroidEnableProfiledAot, memungkinkan pemangkasan (hampir) semua metode yang telah dikompilasi menggunakan AOT. Anda juga dapat menggunakan AOT dan IL stripping dengan profil secara bersama-sama dengan mengatur kedua properti secara eksplisit ke true.

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
  <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>

Aplikasi Windows dibangun lintas platform

Saat Anda membangun aplikasi yang menargetkan Windows pada platform non-Windows, executable yang dihasilkan sekarang diperbarui dengan sumber daya Win32 yang ditentukan—misalnya, ikon aplikasi, manifes, informasi versi.

Sebelumnya, aplikasi harus dibangun di Windows untuk memiliki sumber daya tersebut. Memperbaiki kesenjangan ini dalam dukungan lintas bangunan telah menjadi permintaan populer, karena itu adalah titik nyeri yang signifikan yang memengaruhi kompleksitas infrastruktur dan penggunaan sumber daya.

Lihat juga