【翻译】.NET 5 RC1发布
9月14日,.NET5發布了(Release Candidate)RC1版本,RC的意思是指我們可以進行使用,并且RC版本得到了支持,該版本是接近.NET5的版本,也是11月正式版本之前兩個RC版本中的其中一個。目前,開發團隊正在尋找在.NET5發布之前剩余的bug,當然他們也希望我們的反饋以幫助他們順利的完成.NET5的開發計劃。
開發團隊在今天還發布了ASP.NET Core和EF Core的RC1版本。
現在我們可以進行下載用于Windows、macOS和Linux的.NET5
Installers and binaries
Container images
Snap installer
Release notes
Known issues
GitHub issue tracker
如果要使用.NET5,我們需要使用最新的Visual Studio預覽版(包括Visual Studio for Mac)
在.NET5中有許多的改進,特別是對單文件可執行應用程序、更小的容器映像、更強大的JsonSerializer api、BCL nullable reference type annotated、新target framework names,以及對Windows ARM64的支持。在.NET庫中,GC和JIT的性能都得到了極大的提升,ARM64是性能優化的重點,它為我們帶來了更好的吞吐量和更小的二進制文件。.NET5.0包含了新的語言版本,C#9和F#5.0。
下面還有他們最近發布的一些有關于.NET5.0新功能的文章,大家可以閱讀一下:
F# 5 update for August
ARM64 Performance in .NET 5
Improvements in native code interop in .NET 5.0
Introducing the Half type!
App Trimming in .NET 5
Customizing Trimming in .NET 5
Automatically find latent bugs in your code with .NET 5
其實就像在.NET5 Preview8中一樣,在本章還是像上一章一樣選擇了一些特性來進行深入的研究介紹,在本章中將深入的討論C#9中新特性records和System.Text.Json.JsonSerializer,它們是獨立的特性,但也是很好的一個組合,特別是在我們花費一些時間去為反序列化的JSON對象設計POCO類型時。
C# 9 — Records
Records可能是c#9中最重要的一個新特性,它們提供了一個廣泛的特性集(對于一種語言類型),其中一些需要RC1或更高的版本(如record.ToString())。
將records看作不可變類是最簡單的方式,在特性方面,它們很接近元組(Tuple),可以將他們視為具有屬性和不可變性的自定義元組。在今天使用元組的許多情況下,records可以更好的提供這些元組。
如果你正在使用C#,你會得到最好的體驗,如果你使用命名類型(相對于像元組這樣的特性)。靜態類型是該語言主要的設計要點,records使小型類型更容易使用,并在整個應用程序中利用類型安全。
Records are immutable data types
Records使我們能夠創建不可變的數據類型,這對于定義存儲少量數據的類型非常有用。
下面是一個records的示例,它存儲登錄用戶信息.
public record LoginResource(string Username, string Password, bool RememberMe);在語義中與下面的幾乎完全相同,當然下面將會很快的去介紹這些的差異性。
public class LoginResource {public LoginResource(string username, string password, bool rememberMe){Username = username;Password = password;RememberMe = rememberMe;}public string Username { get; init; }public string Password { get; init; }public bool RememberMe { get; init; } }init是一個新的關鍵字,它是set的代替,set允許我們在任何時候分配一個屬性,init只允許在對象構建期間進行屬性的賦值操作,它是records的不變性所依賴的基礎,任何類型都可以使用init。正如我們在前面的定義中所看到的那樣,它不是特定于records的。
private set看起來類似于init;private set防止其他代碼(類型以外的代碼)改變數據,當類型(在構建之后)意外的改變屬性時,init將在編譯器生成時返回錯誤。private set并非旨在為不可變數據建模,因此當類型在構造后使屬性值發生沖突時,private set不會產生任何編輯器錯誤或者警告。
Records are specialized classes
正如上面提到的LoginResource的records的變量和類變量幾乎是相同的,類定義是記錄的一個語義相同的子集,records 提供了更多的、專門的行為。
下面是比較一個record和一個使用init而不是set作為屬性類之間的比較。
有什么相同?
Construction
Immutability
Copy semantics (records are classes under the hood)
有什么不同?
records相等性是基于內容的。基于對象標識的類相等性
records提供了一個GetHashCode()實現,它基于record內容
records提供一個IEquatable
實現。它使用唯一的GetHashCode()行為作為機制,為record提供基于內容的相等語義。覆蓋Record ToString()以打印record內容。
record和類(使用init)之間的差異可以在LoginResource作為記錄和LoginResource作為類的反匯編中看到。
下面代碼片段中將演示這些差異
using System; using System.Linq; using static System.Console;var user = "Lion-O"; var password = "jaga"; var rememberMe = true; LoginResourceRecord lrr1 = new(user, password, rememberMe); var lrr2 = new LoginResourceRecord(user, password, rememberMe); var lrc1 = new LoginResourceClass(user, password, rememberMe); var lrc2 = new LoginResourceClass(user, password, rememberMe);WriteLine($"Test record equality -- lrr1 == lrr2 : {lrr1 == lrr2}"); WriteLine($"Test class equality -- lrc1 == lrc2 : {lrc1 == lrc2}"); WriteLine($"Print lrr1 hash code -- lrr1.GetHashCode(): {lrr1.GetHashCode()}"); WriteLine($"Print lrr2 hash code -- lrr2.GetHashCode(): {lrr2.GetHashCode()}"); WriteLine($"Print lrc1 hash code -- lrc1.GetHashCode(): {lrc1.GetHashCode()}"); WriteLine($"Print lrc2 hash code -- lrc2.GetHashCode(): {lrc2.GetHashCode()}"); WriteLine($"{nameof(LoginResourceRecord)} implements IEquatable<T>: {lrr1 is IEquatable<LoginResourceRecord>} "); WriteLine($"{nameof(LoginResourceClass)} implements IEquatable<T>: {lrr1 is IEquatable<LoginResourceClass>}"); WriteLine($"Print {nameof(LoginResourceRecord)}.ToString -- lrr1.ToString(): {lrr1.ToString()}"); WriteLine($"Print {nameof(LoginResourceClass)}.ToString -- lrc1.ToString(): {lrc1.ToString()}");public record LoginResourceRecord(string Username, string Password, bool RememberMe);public class LoginResourceClass {public LoginResourceClass(string username, string password, bool rememberMe){Username = username;Password = password;RememberMe = rememberMe;}public string Username { get; init; }public string Password { get; init; }public bool RememberMe { get; init; } }注意:我們會注意到LoginResource類型以Record和Class結束。該模式并不是新的命名模式的規范,這樣命名只是為了我們在代碼片段中有相同類型的record和類變量。請不要這樣命名我們的類型。
如下是上面代碼的輸出內容
rich@thundera records % dotnet run Test record equality -- lrr1 == lrr2 : True Test class equality -- lrc1 == lrc2 : False Print lrr1 hash code -- lrr1.GetHashCode(): -542976961 Print lrr2 hash code -- lrr2.GetHashCode(): -542976961 Print lrc1 hash code -- lrc1.GetHashCode(): 54267293 Print lrc2 hash code -- lrc2.GetHashCode(): 18643596 LoginResourceRecord implements IEquatable<T>: True LoginResourceClass implements IEquatable<T>: False Print LoginResourceRecord.ToString -- lrr1.ToString(): LoginResourceRecord { Username = Lion-O, Password = jaga, RememberMe = True } Print LoginResourceClass.ToString -- lrc1.ToString(): LoginResourceClassRecord syntax
有多種用于聲明records的用例,在使用過每種方式后,我們就會對每一種模式的好處有所了解,我們還能看到不同方式,他們不是不同的語法而是多種選擇。
第一個方式是最簡單的,但是它的靈活性比較小,它適用于具有少量必需屬性的records。
下面是前面顯示的LoginResource record,作為此模式的一個示例。這一行是的定義
public record LoginResource(string Username, string Password, bool RememberMe);構造遵循具有參數的構造函數的要求(包括允許使用可選參數)。
var login = new LoginResource("Lion-O", "jaga", true);還可以使用目標類型。
LoginResource login = new("Lion-O", "jaga", true);下一個語法使所有屬性都是可選的。為record提供了一個隱式無參數構造函數。
public record LoginResource {public string Username {get; init;}public string Password {get; init;}public bool RememberMe {get; init;} }構造使用對象初始化器,看起來像下面這樣
LoginResource login = new() {Username = "Lion-O",TemperatureC = "jaga" };如果我們想讓這兩個屬性是必須的,另一個是可選屬性,那么我們可以通過如下方式實現
public record LoginResource(string Username, string Password) {public bool RememberMe {get; init;} }構造可能如下所示,其中未指定RememberMe
LoginResource login = new("Lion-O", "jaga");如果說要指定RememberMe可以通過如下方式來實現
LoginResource login = new("Lion-O", "jaga") {RememberMe = true };如果說我們不認為record只用于不可變數據,那么我們可以選擇公開可變屬性,如下代碼片段所示,該片段展示了關于電池的信息。Model和TotalCapacityAmpHours屬性是不可變的,而剩余的容量百分比是可變的。
using System;Battery battery = new Battery("CR2032", 0.235) {RemainingCapacityPercentage = 100 };Console.WriteLine (battery);for (int i = battery.RemainingCapacityPercentage; i >= 0; i--) {battery.RemainingCapacityPercentage = i; }Console.WriteLine (battery);public record Battery(string Model, double TotalCapacityAmpHours) {public int RemainingCapacityPercentage {get;set;} }輸出結果如下所示:
rich@thundera recordmutable % dotnet run Battery { Model = CR2032, TotalCapacityAmpHours = 0.235, RemainingCapacityPercentage = 100 } Battery { Model = CR2032, TotalCapacityAmpHours = 0.235, RemainingCapacityPercentage = 0 }Non-destructive record mutation
不變性是給我們帶來了很多的好處,但是我們也很快的發現了需要修改record的情況,在不放棄record的情況下,我們該如何處理這種情況呢?with表達式可以滿足這些需求,它可以根據相同類型的現有record來創建新record,我們可以指定想要的不同的新值,并從現有的record中復制所有其他屬性.
現在我們有個需求就是將用戶名轉換為小寫,這樣的情況下我們才可以將其保存到我們的數據庫中,如果說處理這個需求我們可能會像如下代碼片段中這樣去處理:
LoginResource login = new("Lion-O", "jaga", true); LoginResource loginLowercased = lrr1 with {Username = login.Username.ToLowerInvariant()};登錄record沒有被更改,事實上,這是不可能的,轉換只影響了loginLowercased,除了小寫轉換為loginLowercased之外其他與登錄相同。
我們可以使用內置的ToString()覆蓋檢查with是否完成了預期的工作。
Console.WriteLine(login); Console.WriteLine(loginLowercased);下面代碼是輸出
LoginResource { Username = Lion-O, Password = jaga, RememberMe = True } LoginResource { Username = lion-o, Password = jaga, RememberMe = True }我們可以進一步的了解with的工作原理,它將所有的值從一條record復制到另一條record。這不是一個record依賴于另一個record的委托模型。事實上with操作完成后,兩個record之間就沒有關系了,只對record的構建有意義,這就意味著對于引用類型,副本只是引用副本。對于值類型,復制值.
您可以使用以下代碼查看該語義。
Console.WriteLine($"Record equality: {login == loginLowercased}"); Console.WriteLine($"Property equality: Username == {login.Username == loginLowercased.Username}; Password == {login.Password == loginLowercased.Password}; RememberMe == {login.RememberMe == loginLowercased.RememberMe}");輸出:
Record equality: False Property equality: Username == False; Password == True; RememberMe == TrueRecord inheritance
擴展record很容易,假設一個新的LastLoggedIn屬性,可以將其直接添加到LoginResource,record不像傳統的接口那樣脆弱,除非我們想創建需要構造函數參數的新屬性.
這個新的record可以基于如下的LoginResource
public record LoginResource(string Username, string Password) {public bool RememberMe {get; init;} }新的record可能就是如下這樣
public record LoginWithUserDataResource(string Username, string Password, DateTime LastLoggedIn) : LoginResource(Username, Password) {public int DiscountTier {get; init};public bool FreeShipping {get; init}; }現在已經將LastLoggedIn設置為一個必須的屬性,并且也增加了可選的屬性
Modeling record construction helpers
我們一起來看另一個例子,測量體重,體重的測量來自一個互聯網的秤,重量是以公斤來指定的,但是某些情況下,重點需要以磅來提供。
可以通過如下代碼片段進行聲明
public record WeightMeasurement(DateTime Date, int Kilograms) {public int Pounds {get; init;}public static int GetPounds(int kilograms) => kilograms * 2.20462262; }這就是構造的樣子
var weight = 200; WeightMeasurement measurement = new(DateTime.Now, weight) {Pounds = WeightMeasurement.GetPounds(weight) };在本例中,有必要將權重指定為local。不可能在對象初始化器中訪問公斤屬性。還需要將GetPounds定義為靜態方法。不可能在對象初始化器中調用實例方法(對于正在構造的類型)。
Records and Nullability
一切都是不可變的,那么空值從何而來?不完全是。不可變屬性可以是null,并且在這種情況下將始終是null。
讓我們看看另一個沒有啟用可空性的程序。
using System; using System.Collections.Generic;Author author = new(null, null);Console.WriteLine(author.Name.ToString());public record Author(string Name, List<Book> Books) {public string Website {get; init;}public string Genre {get; init;}public List<Author> RelatedAuthors {get; init;} }public record Book(string name, int Published, Author author);這個程序將編譯并拋出一個NullReference異常,這是由于取消引用author.Name為空。
為了進一步說明這一點,將不編譯以下內容。author.Name 初始化為null,然后不能更改,因為屬性是不可變的。
Author author = new(null, null); author.Name = "Colin Meloy";下面啟動可空性
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net5.0</TargetFramework><LangVersion>preview</LangVersion><Nullable>enable</Nullable></PropertyGroup></Project>下面我們能看到一堆這樣的警告
/Users/rich/recordsnullability/Program.cs(8,21): warning CS8618: Non-nullable property 'Website' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/Users/rich/recordsnullability/recordsnullability.csproj]用null注釋更新了Author record,這些注釋描述了我打算使用的record。
public record Author(string Name, List<Book> Books) {public string? Website {get; init;}public string? Genre {get; init;}public List<Author>? RelatedAuthors {get; init;} }仍然得到了對null的警告,null構造的Author之前看到。
/Users/rich/recordsnullability/Program.cs(5,21): warning CS8625: Cannot convert null literal to non-nullable reference type. [/Users/rich/recordsnullability/recordsnullability.csproj]很好,因為我們想避免這種情況?,F在,下面展示該程序的更新版本,該版本可以很好地運行并享有可空性的好處。
using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis;Author lord = new Author("Karen Lord") {Website = "https://karenlord.wordpress.com/",RelatedAuthors = new() };lord.Books.AddRange(new Book[]{new Book("The Best of All Possible Worlds", 2013, lord),new Book("The Galaxy Game", 2015, lord)} );lord.RelatedAuthors.AddRange(new Author[]{new ("Nalo Hopkinson"),new ("Ursula K. Le Guin"),new ("Orson Scott Card"),new ("Patrick Rothfuss")} );Console.WriteLine($"Author: {lord.Name}"); Console.WriteLine($"Books: {lord.Books.Count}"); Console.WriteLine($"Related authors: {lord.RelatedAuthors.Count}");public record Author(string Name) {private List<Book> _books = new();public List<Book> Books => _books;public string? Website {get; init;}public string? Genre {get; init;}public List<Author>? RelatedAuthors {get; init;} }public record Book(string name, int Published, Author author);該程序在編譯時不會出現可空的警告。
大家可能對下面這句有疑惑
lord.RelatedAuthors.AddRange(Author.RelatedAuthors可以為null。編譯器可以看到,RelatedAuthors屬性的設置只是前面幾行,因此它知道RelatedAuthors引用將為非null。
但是,想象一下這個程序看起來是這樣的。
Author GetAuthor() {return new Author("Karen Lord"){Website = "https://karenlord.wordpress.com/",RelatedAuthors = new()}; }Author lord = GetAuthor();編譯器沒有流程分析技巧,無法知道當類型構造在單獨的方法中時,RelatedAuthor將為非空。在這種情況下,將需要以下兩種模式之一
lord.RelatedAuthors!.AddRange(or
if (lord.RelatedAuthors is object) {lord.RelatedAuthors.AddRange( ... }這是一個關于記錄可空性的冗長演示,只是為了說明它不會改變使用可空引用類型的任何體驗。
另外,您可能已經注意到,我將Author record上的Books屬性移動為初始化的get-only屬性,而不是記錄構造函數中的必需參數。這是由于作者與書籍之間存在循環關系。不變性和循環引用可能會引起頭痛。在這種情況下可以,并且僅表示需要在Book對象之前創建所有Author對象。結果,無法提供完全初始化的Book對象集作為Author結構的一部分。作為Author結構的一部分,我們可以期望的最好的是一個空的List。結果,初始化空的List作為Author結構的一部分似乎是最佳選擇。沒有規則要求所有這些屬性都必須是init樣式。這樣做只是為了演示該行為。
我們將過渡到談論JSON序列化。這個帶有循環引用的示例與不久之后的在JSON對象圖中保存引用有關。JsonSerializer支持帶有循環引用的對象圖,但不支持帶有參數化構造函數的類型。您可以將Author對象序列化為JSON,但不能序列化為當前定義的Author對象。如果Author不是記錄或沒有循環引用,那么JsonSerializer可以同時進行序列化和反序列化。
System.Text.Json
.NET 5.0中對System.Text.Json進行了顯著改進,以提高性能,可靠性,當然如果熟悉Newtonsoft.Json那么用起來更容易, 它還包括對將JSON對象反序列化為記錄的支持,本文前面已介紹了新的C#功能
如果要使用System.Text.Json替代Newtonsoft.Json,則應查看遷移指南。該指南闡明了這兩個API之間的關系。System.Text.Json旨在涵蓋與Newtonsoft.Json相同的許多場景,但并不旨在替代流行的JSON庫或與流行的JSON庫實現功能對等。我們嘗試在性能和可用性之間保持平衡,并在設計選擇中偏向性能。
HttpClient extension methods
JsonSerializer擴展方法現在在HttpClient上公開,并且極大地簡化了同時使用這兩個api。這些擴展方法消除了復雜性,并為您處理各種場景,包括處理內容流和驗證內容媒體類型。Steve Gordon很好地解釋了使用帶有System.Net.Http.Json的HttpClient發送和接收JSON的好處。
下面的示例使用新的GetFromJsonAsync()擴展方法將天氣預報JSON數據反序列化為預報記錄。
using System; using System.Net.Http; using System.Net.Http.Json;string serviceURL = "https://localhost:5001/WeatherForecast"; HttpClient client = new(); Forecast[] forecasts = await client.GetFromJsonAsync<Forecast[]>(serviceURL);foreach(Forecast forecast in forecasts) {Console.WriteLine($"{forecast.Date}; {forecast.TemperatureC}C; {forecast.Summary}"); }// {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} public record Forecast(DateTime Date, int TemperatureC, int TemperatureF, string Summary);這段代碼非常緊湊!它依賴于來自c#9的頂級程序和record,以及新的GetFromJsonAsync()擴展方法。在foreach和await的使用中可能大家會懷疑是否對流JSON對象的支持,在未來版本中是支持的。
大家可以在自己的機器上試試。下面的.NET SDK命令將使用WebAPI模板創建一個天氣預報服務。默認情況下,它將在以下URL公開服務:https://localhost:5001/WeatherForecast。這與示例中使用的URL相同。
rich@thundera ~ % dotnet new webapi -o webapi rich@thundera ~ % cd webapi rich@thundera webapi % dotnet run確保已經運行dotnet dev-certs https——首先信任,否則客戶端和服務器之間的握手將不起作用。如果有問題,請參見信任ASP.NET Core HTTPS開發證書。
然后可以運行前面的示例。
rich@thundera ~ % git clone https://gist.github.com/3b41d7496f2d8533b2d88896bd31e764.git weather-forecast rich@thundera ~ % cd weather-forecast rich@thundera weather-forecast % dotnet run 9/9/2020 12:09:19 PM; 24C; Chilly 9/10/2020 12:09:19 PM; 54C; Mild 9/11/2020 12:09:19 PM; -2C; Hot 9/12/2020 12:09:19 PM; 24C; Cool 9/13/2020 12:09:19 PM; 45C; BalmyImproved support for immutable types
其實定義不可變類型有多種方式,records只是最新的一種,JsonSerializer現在支持不可變類型
在下面示例中,我們將看到帶有不可變結構的序列化
using System; using System.Text.Json; using System.Text.Json.Serialization;var json = "{\"date\":\"2020-09-06T11:31:01.923395-07:00\",\"temperatureC\":-1,\"temperatureF\":31,\"summary\":\"Scorching\"} "; var options = new JsonSerializerOptions() {PropertyNameCaseInsensitive = true,IncludeFields = true,PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var forecast = JsonSerializer.Deserialize<Forecast>(json, options);Console.WriteLine(forecast.Date); Console.WriteLine(forecast.TemperatureC); Console.WriteLine(forecast.TemperatureF); Console.WriteLine(forecast.Summary);var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options);Console.WriteLine(roundTrippedJson);public struct Forecast{public DateTime Date {get;}public int TemperatureC {get;}public int TemperatureF {get;}public string Summary {get;}[JsonConstructor]public Forecast(DateTime date, int temperatureC, int temperatureF, string summary) => (Date, TemperatureC, TemperatureF, Summary) = (date, temperatureC, temperatureF, summary); }注意:JsonConstructor屬性需要指定與struct一起使用的構造函數,對于類,如果只有一個構造函數,那么屬性就不是必須的,與records相同。
輸出內容:
rich@thundera jsonserializerimmutabletypes % dotnet run 9/6/2020 11:31:01 AM -1 31 Scorching {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}Support for records
JsonSerializer對records的支持與上面展示的不可變類型的支持幾乎相同,我想在這里顯示的區別是將JSON對象反序列化為一條records,該records公開了參數化的構造函數和可選的init屬性。
在下面代碼片段中包含了對records的定義:
using System; using System.Text.Json;Forecast forecast = new(DateTime.Now, 40) {Summary = "Hot!" };string forecastJson = JsonSerializer.Serialize<Forecast>(forecast); Console.WriteLine(forecastJson); Forecast? forecastObj = JsonSerializer.Deserialize<Forecast>(forecastJson); Console.Write(forecastObj);public record Forecast (DateTime Date, int TemperatureC) {public string? Summary {get; init;} };輸出如下所示:
rich@thundera jsonserializerrecords % dotnet run {"Date":"2020-09-12T18:24:47.053821-07:00","TemperatureC":40,"Summary":"Hot!"} Forecast { Date = 9/12/2020 6:24:47 PM, TemperatureC = 40, Summary = Hot! }Improved Dictionary<K,V> support
JsonSerializer現在支持具有非字符串鍵的字典。我們可以在下面的示例中看到它的樣子。在.NET Core 3.0中,這段代碼可以編譯,但會拋出NotSupportedException異常。
using System; using System.Collections.Generic; using System.Text.Json;Dictionary<int, string> numbers = new () {{0, "zero"},{1, "one"},{2, "two"},{3, "three"},{5, "five"},{8, "eight"},{13, "thirteen"},{21, "twenty one"},{34, "thirty four"},{55, "fifty five"}, };var json = JsonSerializer.Serialize<Dictionary<int, string>>(numbers);Console.WriteLine(json);var dictionary = JsonSerializer.Deserialize<Dictionary<int, string>>(json);Console.WriteLine(dictionary[55]);輸出內容:
rich@thundera jsondictionarykeys % dotnet run {"0":"zero","1":"one","2":"two","3":"three","5":"five","8":"eight","13":"thirteen","21":"twenty one","34":"thirty four","55":"fifty five"} fifty fiveSupport for fields
JsonSerializer現在支持字段。
我們可以在下面的示例中看到它的樣子。在.NET Core 3.0中,JsonSerializer無法對使用字段的類型進行序列化或反序列化。對于具有字段且無法更改的現有類型來說,這是一個問題。有了這個支持,這不再是一個問題。
using System; using System.Text.Json;var json = "{\"date\":\"2020-09-06T11:31:01.923395-07:00\",\"temperatureC\":-1,\"temperatureF\":31,\"summary\":\"Scorching\"} "; var options = new JsonSerializerOptions() {PropertyNameCaseInsensitive = true,IncludeFields = true,PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var forecast = JsonSerializer.Deserialize<Forecast>(json, options);Console.WriteLine(forecast.Date); Console.WriteLine(forecast.TemperatureC); Console.WriteLine(forecast.TemperatureF); Console.WriteLine(forecast.Summary);var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options);Console.WriteLine(roundTrippedJson);public class Forecast{public DateTime Date;public int TemperatureC;public int TemperatureF;public string Summary; }輸出內容:
rich@thundera jsonserializerfields % dotnet run 9/6/2020 11:31:01 AM -1 31 Scorching {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}Preserving references in JSON object graphs
JsonSerializer增加了對在JSON對象圖中保存(循環)引用的支持。它通過存儲在將JSON字符串反序列化回對象時可以重新構建的id來實現這一點。
using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization;Employee janeEmployee = new() {Name = "Jane Doe",YearsEmployed = 10 };Employee johnEmployee = new() {Name = "John Smith" };janeEmployee.Reports = new List<Employee> { johnEmployee }; johnEmployee.Manager = janeEmployee;JsonSerializerOptions options = new() {// NEW: globally ignore default values when writing null or defaultDefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,// NEW: globally allow reading and writing numbers as JSON stringsNumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,// NEW: globally support preserving object references when (de)serializingReferenceHandler = ReferenceHandler.Preserve,IncludeFields = true, // NEW: globally include fields for (de)serializationWriteIndented = true,};string serialized = JsonSerializer.Serialize(janeEmployee, options); Console.WriteLine($"Jane serialized: {serialized}");Employee janeDeserialized = JsonSerializer.Deserialize<Employee>(serialized, options); Console.Write("Whether Jane's first report's manager is Jane: "); Console.WriteLine(janeDeserialized.Reports[0].Manager == janeDeserialized);public class Employee {// NEW: Allows use of non-public property accessor.// Can also be used to include fields "per-field", rather than globally with JsonSerializerOptions.[JsonInclude]public string Name { get; internal set; }public Employee Manager { get; set; }public List<Employee> Reports;public int YearsEmployed { get; set; }// NEW: Always include when (de)serializing regardless of global options[JsonIgnore(Condition = JsonIgnoreCondition.Never)]public bool IsManager => Reports?.Count > 0; }Performance
在.NET 5.0中,JsonSerializer的性能得到了顯著改善。Stephen Toub在.NET 5中的Performance Improvements中涵蓋了JsonSerializer的一些改進。我會在這里再介紹幾個。
Collections (de)serialization
本次對大型集合做了顯著的改進(反序列化時為1.15x-1.5x,序列化時為1.5x-2.4x+)。我們可以在dotnet/runtime #2259中更詳細地看到這些改進。
將.NET 5.0與.NET Core 3.1進行比較,對List(反序列化)的改進特別令人印象深刻。這些變化將在高性能應用程序中非常有意義。
| Deserialize before | 76.40 us | 0.392 us | 0.366 us | 76.37 us | 75.53 us | 76.87 us | 1.2169 | – | – | 8.25 KB |
| After ~1.5x faster | 50.05 us | 0.251 us | 0.235 us | 49.94 us | 49.76 us | 50.43 us | 1.3922 | – | – | 8.62 KB |
| Serialize before | 29.04 us | 0.213 us | 0.189 us | 29.00 us | 28.70 us | 29.34 us | 1.2620 | – | – | 8.07 KB |
| After ~2.4x faster | 12.17 us | 0.205 us | 0.191 us | 12.15 us | 11.97 us | 12.55 us | 1.3187 | – | – | 8.34 KB |
Property lookups — naming convention
使用JSON最常見的問題之一是命名規范與.NET設計準則不匹配。JSON屬性通常是camelCase, .NET屬性和字段通常是PascalCase。我們使用的json序列化器負責在命名約定之間架橋。這不是免費的,至少對.NET Core 3.1來說不是。在.NET5中,這種成本現在可以忽略不計了。
.NET 5.0中大大改進了允許缺少屬性和不區分大小寫的代碼。在某些情況下,速度快約1.75倍。
下面是一個簡單的4個屬性測試類的基準測試,它的屬性名為>7 bytes。
3.1 performance | Method | Mean | Error | StdDev | Median | Min | Max | Gen 0 | Gen 1 | Gen 2 | Allocated | |---------------------------------- |-----------:|--------:|--------:|-----------:|-----------:|-----------:|-------:|------:|------:|----------:| | CaseSensitive_Matching | 844.2 ns | 4.25 ns | 3.55 ns | 844.2 ns | 838.6 ns | 850.6 ns | 0.0342 | - | - | 224 B | | CaseInsensitive_Matching | 833.3 ns | 3.84 ns | 3.40 ns | 832.6 ns | 829.4 ns | 841.1 ns | 0.0504 | - | - | 328 B | | CaseSensitive_NotMatching(Missing)| 1,007.7 ns | 9.40 ns | 8.79 ns | 1,005.1 ns | 997.3 ns | 1,023.3 ns | 0.0722 | - | - | 464 B | | CaseInsensitive_NotMatching | 1,405.6 ns | 8.35 ns | 7.40 ns | 1,405.1 ns | 1,397.1 ns | 1,423.6 ns | 0.0626 | - | - | 408 B |5.0 performance | Method | Mean | Error | StdDev | Median | Min | Max | Gen 0 | Gen 1 | Gen 2 | Allocated | |---------------------------------- |---------:|--------:|--------:|---------:|---------:|---------:|-------:|------:|------:|----------:| | CaseSensitive_Matching | 799.2 ns | 4.59 ns | 4.29 ns | 801.0 ns | 790.5 ns | 803.9 ns | 0.0985 | - | - | 632 B | | CaseInsensitive_Matching | 789.2 ns | 6.62 ns | 5.53 ns | 790.3 ns | 776.0 ns | 794.4 ns | 0.1004 | - | - | 632 B | | CaseSensitive_NotMatching(Missing)| 479.9 ns | 0.75 ns | 0.59 ns | 479.8 ns | 479.1 ns | 481.0 ns | 0.0059 | - | - | 40 B | | CaseInsensitive_NotMatching | 783.5 ns | 3.26 ns | 2.89 ns | 783.5 ns | 779.0 ns | 789.2 ns | 0.1004 | - | - | 632 B |TechEmpower improvement
開發團隊在TechEmpower基準測試中花費了大量的精力來改進.NET的性能。使用TechEmpower JSON基準來驗證這些JsonSerializer改進是很有意義的。現在性能提高了~ 19%,一旦我們將條目更新到.NET5,這將提高.NET5在基準測試中的位置。這個版本的目標是與netty相比更具競爭力,netty是一種常見的Java web服務器。
在dotnet/runtime #37976中詳細介紹了這些更改和性能度量。這里有兩套基準。第一個是使用團隊維護的JsonSerializer性能基準測試來驗證性能。觀察到有~8%的改善。下一部分是關于技術授權的。它測量了滿足TechEmpower JSON基準測試要求的三種不同方法。SerializeWithCachedBufferAndWriter是我們在官方基準測試中使用的
| SerializeWithCachedBufferAndWriter (before) | 155.3 ns | 1.19 ns | 1.11 ns | 155.5 ns | 153.3 ns | 157.3 ns | 0.0038 | – | – | 24 B |
| SerializeWithCachedBufferAndWriter (after) | 130.8 ns | 1.50 ns | 1.40 ns | 130.9 ns | 128.6 ns | 133.0 ns | 0.0037 | – | – | 24 B |
如果我們看一下Min列,我們可以做一些簡單的數學計算:153.3/128.6 = ~1.19。提高了19%。
Closing
本文對records和JsonSerializer有了一個更好的認識。它們只是.NET 5.0眾多改進中的兩個。preivew 8的文章涵蓋了更大的特性集,這為5.0的價值提供了更廣闊的視角。
正如我們所知道的,他們現在沒有在.NET 5.0中添加任何新特性。這些后期的預覽和RC的文章來涵蓋開發團隊已經建立的所有功能。當然大家可以在原文中進行留言,說一下在期望RC2中開發團隊這邊需要詳細介紹的特性。
原文:https://devblogs.microsoft.com/dotnet/announcing-net-5-0-rc-1/
總結
以上是生活随笔為你收集整理的【翻译】.NET 5 RC1发布的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NCF框架揭秘直播来了!红包、抽奖、还有
- 下一篇: .NET Core全Linux开发体验分