NET8 officially released, new changes in C #12

NET8 officially released, new changes in C #12

Although 8 has brought many enhancements, such as artificial intelligence, cloud native, performance, native AOT, etc., I am still most concerned about the C#language and some framework-level changes. Here are some new features in C#12 and the framework that I think are more practical.

最后更新 11/17/2023 5:36 PM
不止dotNET
预计阅读 8 分钟
分类
.NET
标签
.NET C# AOT Native AOT AI

Although 8 has brought many enhancements, such as artificial intelligence, cloud native, performance, native AOT, etc., I am still most concerned about the C#language and some framework-level changes. Here are some new features in C#12 and the framework that I think are more practical.

img

At the. NET Conf 2023 conference,. NET 8 was officially released. NET 8 is a long-term support (LTS) release, which means you can get three years of support and patches. We also plan to upgrade the framework from. NET Core 3.1 to 8. We will share information on how to upgrade when the upgrade is complete.

To use. NET 8, you need to install the relevant SDK, which can be downloaded at this address: https://dotnet.microsoft.com/en-cn/download/dotnet/8.0, or upgrade VS2022 to 17.8.

Although 8 has brought many enhancements, such as artificial intelligence, cloud native, performance, native AOT, etc., I am still most concerned about the C#language and some framework-level changes. The following describes the new features I think are more practical in C#12 and the framework. You can see the official document for all update instructions: https://www.example.com. learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-8

Sequential enhancement

Other types of built-in support

  1. Additional types: Half, Int128, and UInt128 can be serialized. There will be no errors when serializing these types in. NET 7, but the content cannot be obtained normally.
  2. ReadOnlyMemory and Memory types can be serialized.
  3. When the type of T is byte, the serialization result is base64, otherwise it is a json array.
using System.Text.Json;
//输出:[65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Console.WriteLine(JsonSerializer.Serialize(new object[] { Half.MaxValue, Int128.MaxValue, UInt128.MaxValue }));
//输出:"AQIDBAUG"
Console.WriteLine(JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1,2,3,4,5,6}));
//输出:[1,2,3]
Console.WriteLine(JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }));

interface hierarchy

IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
Console.WriteLine(JsonSerializer.Serialize(value));
//输出:{"Base":0,"Derived":1}

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

After the IDerived interface inherits the IBase interface in the above code, it has two attributes.

In previous versions (3.1, 6, and 7), the interface IDeried containing two attributes was used to receive instantiation of objects and then serialize them. The only result was: {Derived":1}, and the inherited attribute Base could not be recognized.

Improvements have been made in 8 and the expected results can be achieved. It is worth noting that if workarounds have been used before, targeted testing and adjustments will be needed after the upgrade.

named policy

The following figure shows the support for naming strategies during serialization in 8:

img

In previous versions: 3.1, 6, and 7, only CamelCase was supported. The new strategies in 8 are as follows:

  • KebabCaseLower: Lower case middle line, for example: user-name.
  • KebabCaseUpper: Capitalized middle line, for example: USER-NAME.
  • SnakeCaseLower: Lower case underscore, for example: user_name.
  • SnakeCaseUpper: Underword in capital, for example: USER_NAME.
var options1 = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
};
var options2 = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
};
var options3 = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};
var options4 = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseUpper,
};
Console.WriteLine(JsonSerializer.Serialize(new UserInfo() { UserName = "oec2003" }, options1));
Console.WriteLine(JsonSerializer.Serialize(new UserInfo() { UserName = "oec2003" }, options2));
Console.WriteLine(JsonSerializer.Serialize(new UserInfo() { UserName = "oec2003" }, options3));
Console.WriteLine(JsonSerializer.Serialize(new UserInfo() { UserName = "oec2003" }, options4));

public class UserInfo
{
    public string? UserName { get; set; }
}

The results are as follows:

img

Call the API to directly obtain the object

Now there is an interface that returns data as shown in the following figure:

img

If you obtain the data of this interface in versions before 8, you need to first obtain the interface content and then deserialize it. The code is as follows:

const string RequestUri = "http://localhost:5145/user";
using var client = new HttpClient();
var stream =await client.GetStreamAsync(RequestUri);
//反序列化
var users = JsonSerializer.DeserializeAsyncEnumerable<UserInfo>(stream);
await foreach(UserInfo user in users)
{
    Console.WriteLine($"姓名:{user.userName}");
}
Console.ReadKey();

public record UserInfo(string userName);

In version 8, you can directly call the GetFromJsonAsAsyncEnumerable method to get the object directly without deserialization:

const string RequestUri = "http://localhost:5145/user";
using var client = new HttpClient();
IAsyncEnumerable<UserInfo> users = client.GetFromJsonAsAsyncEnumerable<UserInfo>(RequestUri);

await foreach (UserInfo user in users)
{
    Console.WriteLine($"姓名: {user.userName}");
}
Console.ReadKey();

public record UserInfo(string userName);

The results of the above two codes are the same, as shown in the following figure:

img

random number enhancement

  1. In 8, the GetItems() method is provided for the random number class Random, which can randomly extract data items from a provided set based on the specified number to generate a new set:
ReadOnlySpan<string> colors = new[]{"Red","Green","Blue","Black"};

string[] t1 = Random.Shared.GetItems(colors, 10);
Console.WriteLine(JsonSerializer.Serialize(t1));

//输出:["Black","Green","Blue","Blue","Green","Blue","Green","Black","Green","Blue"]
//每次都会不一样
Console.ReadKey();
  1. Using the Shuffle() method provided by Random, you can order the data items in a collection:
string[] colors = new[]{"Red","Green","Blue","Black"};
Random.Shared.Shuffle(colors);

Console.WriteLine(JsonSerializer.Serialize(colors));

Console.ReadKey();

New types of performance improvements

  1. 新增了 FrozenDictionary<TKey,TValue>FrozenSet,这两个类型在 System.Collections.Frozen 命名空间下,创建这两种类型的集合后,就不允许对键和值进行任何更改,因此可以实现更快的读取操作。

Here is the code to test FrozenDictionary and Dictionary using BenchmarkDotNet:

BenchmarkRunner.Run<FrozenDicTest>();
Console.ReadKey();

[SimpleJob(RunStrategy.ColdStart, iterationCount:5)]
public class FrozenDicTest
{
    public static Dictionary<string, string> dic = new() {
        { "name1","oec2003"},
        { "name2","oec2004"},
        { "name3","oec2005"}
    };

    public static FrozenDictionary<string, string> fdic = dic.ToFrozenDictionary();

    [Benchmark]
    public void TestDic()
    {
        for (int i = 0; i < 100000000; i++)
        {
            dic.TryGetValue("name", out _);
        }
    }

    [Benchmark]
    public void TestFDic()
    {
        for (int i = 0; i < 100000000; i++)
        {
            fdic.TryGetValue("name", out _);
        }
    }
}

Judging from the test results, the effect is still very obvious:

img

  1. 新增的 System.Buffers.SearchValues类,可以用来进行字符串的查找和匹配,相比较 string 类型的操作,性能有大幅提升,下面还是用 BenchmarkDotNet 进行测试:
BenchmarkRunner.Run<SearchValuesTest>();
Console.ReadKey();

[SimpleJob(RunStrategy.ColdStart, iterationCount: 5)]
public class SearchValuesTest
{
    [Benchmark]
    public void TestString()
    {
        var str = "!@#$%^&*()_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        for (int i = 0; i < 100000000; i++)
        {
            str.Contains("z");
        }
    }

    [Benchmark]
    public void TestSearchValues()
    {
        var sv = SearchValues.Create("!@#$%^&*()_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"u8);
        byte b = (byte)"z"[0];
        for (int i = 0; i < 100000000; i++)
        {
            sv.Contains(b);
        }
    }
}

Judging from the operation results, there is an improvement of about 5 times:

img

Dependency injection enhancement

In versions before 8, dependency injection was written as follows:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IUser, UserA>();

var app = builder.Build();

app.MapGet("/user", (IUser user) =>
{
    return $"hello , {user.GetName()}";
});

app.Run();

internal interface IUser
{
    string GetName();
}
internal class UserA: IUser
{
    public string GetName() => "oec2003";
}

If there are two implementations of the IUser interface, the writing in the above code can only obtain an instance of the last registered class. To inject multiple implementation classes into one interface, some additional code needs to be written, which is quite cumbersome.

The injection keyword was added in version 8, which can be easily implemented. See the following code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<IUser, UserA>("A");
builder.Services.AddKeyedSingleton<IUser, UserB>("B");

var app = builder.Build();

app.MapGet("/user1", ([FromKeyedServices("A")] IUser user) =>
{
    return $"hello , {user?.GetName()}";
});
app.MapGet("/user2", ([FromKeyedServices("B")] IUser user) =>
{
    return $"hello , {user?.GetName()}";
});

app.Run();

internal interface IUser
{
    string GetName();
}
internal class UserA: IUser
{
    public string GetName() => "oec2003";
}
internal class UserB : IUser
{
    public string GetName() => "oec2004";
}
Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2/7/2026

Summary of experience in using AOT

From the very beginning of project creation, you should develop a good habit of conducting AOT release testing in a timely manner whenever new features are added or newer syntax is used.

继续阅读
同分类 / 同标签 1/5/2026

A 2025 summary for all. NET developers

I believe you have seen many articles like "Sorry, C#has fallen out of the top echelon" this year. What is the. NET ecosystem? This article will systematically sort out the technology trends and important events that. NET developers should pay attention to most in 2025, covering the latest developments and trends in AI development,. NET evolution and the integration of the two, in order to help everyone find the right position and meet future challenges and opportunities.

继续阅读
同分类 / 同标签 2/25/2025

NET 10 Preview 1 released

Today. NET 10 Preview 1 was released. I downloaded it as soon as possible and upgraded the Avalonia UI project and blog site. The former's functional testing and AOT release were normal, the latter's debugging was normal, and Docker was not successful for the time being.

继续阅读