今天我們釋出了 .NET 7 預覽版 5。.NET 7 的這個預覽版包括對通用數學的改進,方便了 API 作者,使其更輕鬆,一個新的 ML.NET 文字分類 API,增加了最先進的深度學習技術 ,對於自然語言處理,對原始碼生成器的各種改進以及用於 RegexGenerator 的新 Roslyn 分析器和修復器,以及在 CodeGen、可觀察性、JSON 序列化/反序列化和使用流方面的多項效能改進。
您可以下載適用於 Windows、macOS 和 Linux 的 .NET 7 Preview 5。
.NET 7 預覽版 5 已通過 Visual Studio 17.3 預覽版 2 進行測試。如果您想將 .NET 7 與 Visual Studio 系列產品一起使用,我們建議您使用預覽頻道版本。如果您使用的是 macOS,我們建議使用最新的 Visual Studio 2022 for Mac 預覽版。現在,讓我們瞭解此版本中的一些最新更新。
可觀察性
可觀察性的目標是幫助您更好地瞭解應用程式在規模和技術複雜性增加時的狀態。
▌公開高效的 ActivityEvent 和 ActivityLink 標記列舉器方法
公開的方法可用於在效能關鍵場景中列舉 Tag 物件,而無需任何額外的分配和快速的專案訪問。
var tags = new List<KeyValuePair<string, object?>>()
{
new KeyValuePair<string, object?>("tag1", "value1"),
new KeyValuePair<string, object?>("tag2", "value2"),
};
ActivityLink link = new ActivityLink(default, new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in link.EnumerateTagObjects())
{
// Consume the link tags without any extra allocations or value copying.
}
ActivityEvent e = new ActivityEvent("SomeEvent", tags: new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in e.EnumerateTagObjects())
{
// Consume the event's tags without any extra allocations or value copying.
}
可觀察性:
https://devblogs.microsoft.com/dotnet/opentelemetry-net-reaches-v1-0/?ocid=AID3042760
System.Text.Json多型性
#63747
System.Text.Json 現在支援使用屬性註釋對多型型別層次結構進行序列化和反序列化:
[JsonDerivedType(typeof(Derived))]
public class Base
{
public int X { get; set; }
}
public class Derived : Base
{
public int Y { get; set; }
}
此配置為 Base 啟用多型序列化,特別是在執行時型別為 Derived 時:
Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "X" : 0, "Y" : 0 }
請注意,這不會啟用多型反序列化,因為有效負載將作為 Base 往返:
Base value = JsonSerializer.Deserialize<Base>(@"{ ""X"" : 0, ""Y"" : 0 }");
value is Derived; // false
▌使用型別鑑別器
要啟用多型反序列化,使用者需要為派生類指定型別鑑別器:
[JsonDerivedType(typeof(Base), typeDiscriminator: "base")]
[JsonDerivedType(typeof(Derived), typeDiscriminator: "derived")]
public class Base
{
public int X { get; set; }
}
public class Derived : Base
{
public int Y { get; set; }
}
現在將發出 JSON 以及型別鑑別器後設資料:
Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "$type" : "derived", "X" : 0, "Y" : 0 }
可用於多型反序列化值:
Base value = JsonSerializer.Deserialize<Base>(@"{ ""$type"" : ""derived"", ""X"" : 0, ""Y"" : 0 }");
value is Derived; // true
型別鑑別器識別符號也可以是整數,因此以下形式是有效的:
[JsonDerivedType(typeof(Derived1), 0)]
[JsonDerivedType(typeof(Derived2), 1)]
[JsonDerivedType(typeof(Derived3), 2)]
public class Base { }
JsonSerializer.Serialize<Base>(new Derived2()); // { "$type" : 1, ... }
#63747:
https://github.com/dotnet/runtime/issues/63747
▌Utf8JsonReader.CopyString
#54410
直到今天,Utf8JsonReader.GetString() 一直是使用者使用解碼後的 JSON 字串的唯一方式。這將始終分配一個新字串,這可能不適合某些效能敏感的應用程式。新包含的 CopyString 方法允許將未轉義的 UTF-8 或 UTF-16 字串複製到使用者擁有的緩衝區:
int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.Slice(0, charsRead);
ParseUnescapedString(source); // handle the unescaped JSON string
ArrayPool<char>.Shared.Return(buffer);
或者如果處理 UTF-8 更可取:
ReadOnlySpan<byte> source = stackalloc byte[0];
if (!reader.HasReadOnlySequence && !reader.ValueIsEscaped)
{
source = reader.ValueSpan; // No need to copy to an intermediate buffer if value is span without escape sequences
}
else
{
int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
Span<byte> buffer = valueLength <= 256 ? stackalloc byte[256] : new byte[valueLength];
int bytesRead = reader.CopyString(buffer);
source = buffer.Slice(0, bytesRead);
}
ParseUnescapedBytes(source);
▌源生成改進
新增了對 IAsyncEnumerable<T> (#59268)、JsonDocument(#59954) 和 DateOnly/TimeOnly(#53539) 型別的原始碼生成支援。
例如:
[JsonSerializable(typeof(typeof(MyPoco))]
public class MyContext : JsonSerializerContext {}
public class MyPoco
{
// Use of IAsyncEnumerable that previously resulted
// in JsonSerializer.Serialize() throwing NotSupportedException
public IAsyncEnumerable<int> Data { get; set; }
}
// It now works and no longer throws NotSupportedException
JsonSerializer.Serialize(new MyPoco { Data = ... }, MyContext.MyPoco);
System.IO.Stream 、 ReadExactly 和 ReadAtLeast
使用 Stream.Read() 時最常見的錯誤之一是 Read() 返回的資料可能比 Stream 中可用的資料少,而資料也比傳入的緩衝區少。即使對於意識到這一點的程式設計師來說, 每次他們想從 Stream 中讀取時都編寫相同的迴圈很煩人。
為了解決這種情況,我們在 System.IO.Stream 基類中新增了新方法:
namespace System.IO;
public partial class Stream
{
public void ReadExactly(Span<byte> buffer);
public void ReadExactly(byte[] buffer, int offset, int count);
public ValueTask ReadExactlyAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
public ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default);
public int ReadAtLeast(Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true);
public ValueTask<int> ReadAtLeastAsync(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default);
}
新的 ReadExactly 方法保證準確讀取請求的位元組數。如果流在讀取請求的位元組之前結束,則丟擲 EndOfStreamException。
using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];
f.ReadExactly(buffer); // guaranteed to read 100 bytes from the file
新的 ReadAtLeast 方法將至少讀取請求的位元組數。如果有更多資料可用,它可以讀取更多資料,直到緩衝區的大小。如果流在讀取請求的位元組之前結束,則會引發 EndOfStreamException(在高階情況下,當您想要 ReadAtLest 的好處但您還想自己處理流結束場景時,您可以選擇不引發異常)。
using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];
int bytesRead = f.ReadAtLeast(buffer, 10);
// 10 <= bytesRead <= 100
RegexGenerator 的新 Roslyn 分析器和修復器
在 .NET 7 中的正規表示式改進中,Stephen Toub 描述了新的 RegexGenerator 源生成器,它允許您在編譯時靜態生成正規表示式,從而獲得更好的效能。要利用這一點,首先您必須在程式碼中找到可以使用它的位置,然後對每個程式碼進行更改。這聽起來像是 Roslyn 分析器和修復器的完美工作,所以我們在 Preview 5 中新增了一個。
▌分析儀
新的分析器包含在 .NET 7 中,將搜尋可以轉換為使用 RegexGenerator 源生成器的 Regex 用途。分析器將檢測 Regex 建構函式的使用,以及滿足以下條件的 Regex 靜態方法的使用:
提供的引數在編譯時具有已知值。原始碼生成器的輸出取決於這些值,因此必須在編譯時知道它們。
它們是面向 .NET 7 的應用程式的一部分。新的分析器包含在 .NET 7 目標包中,只有面向 .NET 7 的應用程式才有資格使用此分析器。
LangVersion(瞭解更多)高於 10。目前正規表示式源生成器需要將 LangVersion 設定為預覽。
下面是 Visual Studio 中正在執行的新分析器:
▌程式碼修復器
程式碼修復程式也包含在 .NET 7 中,它做了兩件事。首先,它建議使用 RegexGenerator 源生成器方法,併為您提供覆蓋預設名稱的選項。然後它用對新方法的呼叫替換原始程式碼。
以下是 Visual Studio 中正在執行的新程式碼修復程式:
通用數學
在 .NET 6 中,我們預覽了一個名為 Generic Math 的功能,它允許 .NET 開發人員在通用程式碼中利用靜態 API,包括運算子。此功能將直接使可以簡化程式碼庫的 API 作者受益。其他開發人員將間接受益,因為他們使用的 API 將開始支援更多型別,而不需要每個數字型別都獲得顯式支援。
在 .NET 7 中,我們對實現進行了改進並響應了社群的反饋。有關更改和可用 API 的更多資訊,請參閱我們的通用數學特定公告。
System.Reflection 呼叫成員時的效能改進
#67917
當對同一個成員進行多次呼叫時,使用反射來呼叫成員(無論是方法、建構函式還是屬性 gettersetter)的開銷已大大減少。典型增益快 3-4 倍。
使用 BenchmarkDotNet 包:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;
namespace ReflectionBenchmarks
{
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<InvokeTest>();
}
}
public class InvokeTest
{
private MethodInfo? _method;
private object[] _args = new object[1] { 42 };
[GlobalSetup]
public void Setup()
{
_method = typeof(InvokeTest).GetMethod(nameof(InvokeMe), BindingFlags.Public | BindingFlags.Static)!;
}
[Benchmark]
// *** This went from ~116ns to ~39ns or 3x (66%) faster.***
public void InvokeSimpleMethod() => _method!.Invoke(obj: null, new object[] { 42 });
[Benchmark]
// *** This went from ~106ns to ~26ns or 4x (75%) faster. ***
public void InvokeSimpleMethodWithCachedArgs() => _method!.Invoke(obj: null, _args);
public static int InvokeMe(int i) => i;
}
}
ML.NET 文字分類 API
文字分類是將標籤或類別應用於文字的過程。
常見用例包括:
- 將電子郵件分類為垃圾郵件或非垃圾郵件
- 從客戶評論中分析情緒是積極的還是消極的
- 應用標籤來支援工單
文字分類是分類的一個子集,因此今天您可以使用 ML.NET 中現有的分類演算法來解決文字分類問題。然而,這些演算法並沒有解決文字分類以及現代深度學習技術的常見挑戰。
我們很高興推出 ML.NET 文字分類 API,該 API 使您可以更輕鬆地訓練自定義文字分類模型,並將用於自然語言處理的最新最先進的深度學習技術引入 ML.NET。
有關更多詳細資訊,請參閱我們的 ML.NET 特定公告。
程式碼生成
非常感謝社群貢獻者。
@singleaccretion 在預覽版 5 期間做出了 23 項 PR 貢獻,其中亮點是:
改進冗餘分支優化以處理更多副作用 #68447
PUTARG_STK/x86: 標記 push [mem] 候選 reg 可選 #68641
在 LCL_FLD 上覆制傳播 #68592
@Sandreenko 完成允許 StoreLclVar src 成為 IND/FLD #59315。@hez2010 修復了 #68475 中的 CircleInConvex 測試。
來自@anthonycanino、@aromaa 和@ta264 的更多貢獻包含在後面的部分中。
▌Arm64
#68363 合併“msub”(將兩個暫存器值相乘,從第三個暫存器值中減去乘積)和“madd”(將兩個暫存器值相乘,新增第三個暫存器值)邏輯。
Arm64:讓 CpBlkUnroll 和 InitBlkUnroll 使用 SIMD 暫存器來初始化複製小於 128 位元組的記憶體塊(請參閱效能改進細節)。
▌迴圈優化
#67930 處理迴圈克隆的更多場景現在支援以 > 1 的增量向後或向前的迴圈(請參閱效能改進詳細資訊)。
#68588 提升“this”物件的空值檢查將空值檢查移動到迴圈外的物件上(請參閱效能改進細節)。
x86/x64 優化
- #67182 在 x64 上將 shlx、sarx、shrx 優化為 x64 上的 mov+shl、sar 或 shr 到 shlx、sarx 或 shrx。
- #68091為 x64 啟用了 UMOD 優化。
- @anthonycanino 在 #68677中新增了 X86Serialize 硬體內在。
- @aromaa 在 #66965中將 bswap+mov 優化為movbe。
- @ta264 修復了 #68046 中 clr.alljits 子集的linux-x86 編譯。
一般優化
現代化 JIT
隨著社群增加了對 JIT 程式碼庫的貢獻,重組和現代化我們的程式碼庫以使我們的貢獻者能夠輕鬆地增加和快速開發程式碼變得非常重要。
在 Preview 5 中,我們在內部做了大量工作,清理了 JIT 的中間表示,並消除了過去設計決策帶來的限制。在許多情況下,這項工作導致 JIT 本身的記憶體使用更少和吞吐量更高,而在其他情況下,它導致了更好的程式碼質量。以下是一些亮點:
- 刪除 CLS_VAR #68524
- 刪除 GT_ARGPLACE #68140
- 刪除 GT_PUTARG_TYPE #68748
以上允許我們在使用 byte/sbyte/short/ushort 型別的引數行內函數時消除 JIT 內聯中的舊限制,從而提高程式碼質量(允許內聯替換小引數 #69068)
需要改進的一個領域是更好地理解涉及讀取和寫入結構和結構欄位的不安全程式碼。@SingleAccretion 通過將 JIT 的內部模型轉換為更通用的“物理”模型,在這一領域做出了巨大的改變。這為 JIT 使用 struct reinterpretation 等功能更好地推理不安全程式碼鋪平了道路:
還進行了其他小的清理以簡化 JIT IR:
啟用庫修剪
正如我們之前所描述的,修剪讓 SDK 從您的自包含應用程式中刪除未使用的程式碼,以使它們更小。但是,修剪警告可能表明應用程式與修剪不相容。為了使應用程式相容,它們的所有引用也必須相容。
為此,我們需要庫也採用修剪。在預覽版 5 中,我們努力使用 Roslyn 分析器更輕鬆地查詢和修復庫中的修剪警告。要檢視庫的修剪警告,請將 <IsTrimmable>true</IsTrimmable> 新增到專案檔案中。修復警告後,使用您的庫修剪的應用程式將更小並且與修剪相容。請參閱準備 .NET 庫以進行修剪 - .NET | Microsoft Docs 瞭解有關庫修剪的更多資訊。
面向 .NET 7
要面向 .NET 7,您需要在專案檔案中使用 .NET 7 Target Framework Moniker (TFM)。例如:
<TargetFramework>net7.0</TargetFramework>
全套 .NET 7 TFM,包括特定於操作的 TFM。
- net7.0
- net7.0-安卓
- net7.0-ios
- net7.0-maccatalyst
- net7.0-macos
- net7.0-tvos
- net7.0-windows
我們希望從 .NET 6 升級到 .NET 7 應該很簡單。請報告您在使用 .NET 7 測試現有應用程式的過程中發現的任何重大更改。
支援
.NET 7 是一個短期支援 (STS) 版本,這意味著它將在釋出之日起 18 個月內獲得免費支援和補丁。需要注意的是,所有版本的質量都是相同的。唯一的區別是支撐的長度。有關 .NET 支援政策的更多資訊,請參閱 .NET 和 .NET Core 官方支援政策。
我們最近將“Current當前”名稱更改為“短期支援 (STS)”。我們正在推出這一變化。
重大變化
您可以通過閱讀 .NET 7 中的重大更改文件找到最新的 .NET 7 重大更改列表。它按區域和版本列出了重大更改,並附有詳細說明的連結。
要檢視提出了哪些重大更改但仍在稽核中,請關注 Proposed .NET Breaking Changes GitHub 問題。
路線圖
.NET 版本包括產品、庫、執行時和工具,代表了 Microsoft 內外多個團隊之間的協作。您可以通過閱讀產品路線圖瞭解有關這些領域的更多資訊:
我們感謝您對 .NET 的所有支援和貢獻。請嘗試 .NET 7 Preview 5 並告訴我們您的想法!
長按識別二維碼
關注微軟中國MSDN