C# 10的(de)新特性
前言
我們很高興地(dì / de)宣布 C# 10 作爲(wéi / wèi) .NET 6 和(hé / huò) Visual Studio 2022的(de)一(yī / yì /yí)部分已經發布了(le/liǎo)。在(zài)這(zhè)篇文章中,我們将介紹 C# 10 的(de)許多新功能,這(zhè)些功能使你的(de)代碼更漂亮、更具表現力、更快。閱讀 Visual Studio 2022 公告和(hé / huò).NET 6 公告以(yǐ)了(le/liǎo)解更多信息,包括如何安裝。
Visual Studio 2022 公告:https://aka.ms/vs2022gablog
.NET 6:https://aka.ms/dotnet6-GA
全局和(hé / huò)隐式 usings
using 指令簡化了(le/liǎo)您使用命名空間的(de)方式。C# 10 包括一(yī / yì /yí)個(gè)新的(de)全局 using 指令和(hé / huò)隐式 usings,以(yǐ)減少您需要(yào / yāo)在(zài)每個(gè)文件頂部指定的(de) usings 數量。
全局 using 指令
如果關鍵字 global 出(chū)現在(zài) using 指令之(zhī)前,則 using 适用于(yú)整個(gè)項目:
你可以(yǐ)在(zài)全局 using 指令中使用 using 的(de)任何功能。例如,添加靜态導入類型并使該類型的(de)成員和(hé / huò)嵌套類型在(zài)整個(gè)項目中可用。如果您在(zài)using 指令中使用别名,該别名也(yě)會影響您的(de)整個(gè)項目: 您可以(yǐ)将全局使用放在(zài)任何 .cs 文件中,包括 Program.cs 或專門命名的(de)文件,如 globalusings.cs。全局 usings 的(de)範圍是(shì)當前編譯,一(yī / yì /yí)般對應當前項目。 有關詳細信息,請參閱全局 using 指令。 隐式 usings 隐式 usings 功能會自動爲(wéi / wèi)您正在(zài)構建的(de)項目類型添加通用的(de)全局 using 指令。要(yào / yāo)啓用隐式 usings,請在(zài) .csproj 文件中設置 ImplicitUsings 屬性: 在(zài)新的(de) .NET 6 模闆中啓用了(le/liǎo)隐式 usings 。在(zài)此博客文章中閱讀有關 .NET 6 模闆更改的(de)更多信息。 一(yī / yì /yí)些特定全局 using 指令集取決于(yú)您正在(zài)構建的(de)應用程序的(de)類型。例如,控制台應用程序或類庫的(de)隐式 usings 不(bù)同于(yú) ASP.NET 應用程序的(de)隐式 usings。 有關詳細信息,請參閱此隐式usings文章。 博客文章 https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#net-sdk-c-project-templates-modernized 隐式usings https://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives Combining using 功能 文件頂部的(de)傳統 using 指令、全局 using 指令和(hé / huò)隐式 using 可以(yǐ)很好地(dì / de)協同工作。隐式 using 允許您在(zài)項目文件中包含适合您正在(zài)構建的(de)項目類型的(de) .NET 命名空間。全局 using 指令允許您包含其他(tā)命名空間,以(yǐ)使它們在(zài)整個(gè)項目中可用。代碼文件頂部的(de) using 指令允許您包含項目中僅少數文件使用的(de)命名空間。 無論它們是(shì)如何定義的(de),額外的(de) using 指令都會增加名稱解析中出(chū)現歧義的(de)可能性。如果遇到(dào)這(zhè)種情況,請考慮添加别名或減少要(yào / yāo)導入的(de)命名空間的(de)數量。例如,您可以(yǐ)将全局 using 指令替換爲(wéi / wèi)文件子(zǐ)集頂部的(de)顯式 using 指令。 如果您需要(yào / yāo)删除通過隐式 usings 包含的(de)命名空間,您可以(yǐ)在(zài)項目文件中指定它們: 您還可以(yǐ)添加命名空間,就(jiù)像它們是(shì)全局 using 指令一(yī / yì /yí)樣,您可以(yǐ)将 Using 項添加到(dào)項目文件中,例如: 文件範圍的(de)命名空間 許多文件包含單個(gè)命名空間的(de)代碼。從 C# 10 開始,您可以(yǐ)将命名空間作爲(wéi / wèi)語句包含在(zài)内,後跟分号且不(bù)帶花括号: 他(tā)簡化了(le/liǎo)代碼并删除了(le/liǎo)嵌套級别。隻允許一(yī / yì /yí)個(gè)文件範圍的(de)命名空間聲明,并且它必須在(zài)聲明任何類型之(zhī)前出(chū)現。 有關文件範圍命名空間的(de)更多信息,請參閱命名空間關鍵字文章。 對 lambda 表達式和(hé / huò)方法組的(de)改進 我們對 lambda 的(de)語法和(hé / huò)類型進行了(le/liǎo)多項改進。我們預計這(zhè)些将廣泛有用,并且驅動方案之(zhī)一(yī / yì /yí)是(shì)使 ASP.NET Minimal API 更加簡單。 lambda 的(de)語法 https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#lambda-expression-improvements ASP.NET Minimal APIhttps://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/ lambda 的(de)自然類型 Lambda 表達式現在(zài)有時(shí)具有“自然”類型。這(zhè)意味着編譯器通常可以(yǐ)推斷出(chū) lambda 表達式的(de)類型。 到(dào)目前爲(wéi / wèi)止,必須将 lambda 表達式轉換爲(wéi / wèi)委托或表達式類型。在(zài)大(dà)多數情況下,您會在(zài) BCL 中使用重載的(de) Func<...> 或 Action<...> 委托類型之(zhī)一(yī / yì /yí): 但是(shì),從 C# 10 開始,如果 lambda 沒有這(zhè)樣的(de)“目标類型”,我們将嘗試爲(wéi / wèi)您計算一(yī / yì /yí)個(gè): 你可以(yǐ)在(zài)你最喜歡的(de)編輯器中将鼠标懸停在(zài) var parse 上(shàng),然後查看類型仍然是(shì) Func<string, int>。一(yī / yì /yí)般來(lái)說(shuō),編譯器将使用可用的(de) Func 或 Action 委托(如果存在(zài)合适的(de)委托)。否則,它将合成一(yī / yì /yí)個(gè)委托類型(例如,當您有 ref 參數或有大(dà)量參數時(shí))。 并非所有 lambda 表達式都有自然類型——有些隻是(shì)沒有足夠的(de)類型信息。 例如,放棄參數類型将使編譯器無法決定使用哪種委托類型: lambda 的(de)自然類型意味着它們可以(yǐ)分配給較弱的(de)類型,例如 object 或 Delegate: 當涉及到(dào)表達式樹時(shí),我們結合了(le/liǎo)“目标”和(hé / huò)“自然”類型。如果目标類型是(shì)LambdaExpression 或非泛型 Expression(所有表達式樹的(de)基類型)并且 lambda 具有自然委托類型 D,我們将改爲(wéi / wèi)生成 Expression<D>: 方法組的(de)自然類型 方法組(即沒有參數列表的(de)方法名稱)現在(zài)有時(shí)也(yě)具有自然類型。您始終能夠将方法組轉換爲(wéi / wèi)兼容的(de)委托類型: 現在(zài),如果方法組隻有一(yī / yì /yí)個(gè)重載,它将具有自然類型: lambda 的(de)返回類型 在(zài)前面的(de)示例中,lambda 表達式的(de)返回類型是(shì)顯而(ér)易見的(de),并被推斷出(chū)來(lái)的(de)。情況并非總是(shì)如此: 在(zài) C# 10 中,您可以(yǐ)在(zài) lambda 表達式上(shàng)指定顯式返回類型,就(jiù)像在(zài)方法或本地(dì / de)函數上(shàng)一(yī / yì /yí)樣。返回類型在(zài)參數之(zhī)前。當你指定一(yī / yì /yí)個(gè)顯式的(de)返回類型時(shí),參數必須用括号括起來(lái),這(zhè)樣編譯器或其他(tā)開發人(rén)員不(bù)會太混淆: lambda 上(shàng)的(de)屬性 從 C# 10 開始,您可以(yǐ)将屬性放在(zài) lambda 表達式上(shàng),就(jiù)像對方法和(hé / huò)本地(dì / de)函數一(yī / yì /yí)樣。當有屬性時(shí),lambda 的(de)參數列表必須用括号括起來(lái): 就(jiù)像本地(dì / de)函數一(yī / yì /yí)樣,如果屬性在(zài) AttributeTargets.Method 上(shàng)有效,則可以(yǐ)将屬性應用于(yú) lambda。 Lambda 的(de)調用方式與方法和(hé / huò)本地(dì / de)函數不(bù)同,因此在(zài)調用 lambda 時(shí)屬性沒有任何影響。但是(shì),lambdas 上(shàng)的(de)屬性對于(yú)代碼分析仍然有用,并且可以(yǐ)通過反射發現它們。 structs 的(de)改進 C# 10 爲(wéi / wèi) structs 引入了(le/liǎo)功能,可在(zài) structs (結構)和(hé / huò)類之(zhī)間提供更好的(de)奇偶性。這(zhè)些新功能包括無參數構造函數、字段初始值設定項、記錄結構和(hé / huò) with 表達式。 01 無參數結構構造函數和(hé / huò)字段初始值設定項 在(zài) C# 10 之(zhī)前,每個(gè)結構都有一(yī / yì /yí)個(gè)隐式的(de)公共無參數構造函數,該構造函數将結構的(de)字段設置爲(wéi / wèi)默認值。在(zài)結構上(shàng)創建無參數構造函數是(shì)錯誤的(de)。 從 C# 10 開始,您可以(yǐ)包含自己的(de)無參數結構構造函數。如果您不(bù)提供,則将提供隐式無參數構造函數以(yǐ)将所有字段設置爲(wéi / wèi)默認值。您在(zài)結構中創建的(de)無參數構造函數必須是(shì)公共的(de)并且不(bù)能是(shì)部分的(de): 您可以(yǐ)如上(shàng)所述在(zài)無參數構造函數中初始化字段,也(yě)可以(yǐ)通過字段或屬性初始化程序初始化它們: 通過默認創建或作爲(wéi / wèi)數組分配的(de)一(yī / yì /yí)部分創建的(de)結構會忽略顯式無參數構造函數,并始終将結構成員設置爲(wéi / wèi)其默認值。有關結構中無參數構造函數的(de)更多信息,請參閱結構類型。 02 Record structs 從 C# 10 開始,現在(zài)可以(yǐ)使用 record struct 定義 record。這(zhè)些類似于(yú) C# 9 中引入的(de)record 類: 您可以(yǐ)繼續使用 record 定義記錄類,也(yě)可以(yǐ)使用 record 類來(lái)清楚地(dì / de)說(shuō)明。 結構已經具有值相等——當你比較它們時(shí),它是(shì)按值。記錄結構添加 IEquatable<T> 支持和(hé / huò) == 運算符。記錄結構提供 IEquatable<T> 的(de)自定義實現以(yǐ)避免反射的(de)性能問題,并且它們包括記錄功能,如 ToString() 覆蓋。 記錄結構可以(yǐ)是(shì)位置的(de),主構造函數隐式聲明公共成員: 主構造函數的(de)參數成爲(wéi / wèi)記錄結構的(de)公共自動實現屬性。與 record 類不(bù)同,隐式創建的(de)屬性是(shì)讀/寫的(de)。這(zhè)使得将元組轉換爲(wéi / wèi)命名類型變得更加容易。将返回類型從 (string FirstName, string LastName) 之(zhī)類的(de)元組更改爲(wéi / wèi) Person 的(de)命名類型可以(yǐ)清理您的(de)代碼并保證成員名稱一(yī / yì /yí)緻。聲明位置記錄結構很容易并保持可變語義。 如果您聲明一(yī / yì /yí)個(gè)與主要(yào / yāo)構造函數參數同名的(de)屬性或字段,則不(bù)會合成任何自動屬性并使用您的(de)。 要(yào / yāo)創建不(bù)可變的(de)記錄結構,請将 readonly 添加到(dào)結構(就(jiù)像您可以(yǐ)添加到(dào)任何結構一(yī / yì /yí)樣)或将 readonly 應用于(yú)單個(gè)屬性。對象初始化器是(shì)可以(yǐ)設置隻讀屬性的(de)構造階段的(de)一(yī / yì /yí)部分。這(zhè)隻是(shì)使用不(bù)可變記錄結構的(de)一(yī / yì /yí)種方法: 在(zài)本文中了(le/liǎo)解有關記錄結構的(de)更多信息。 https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record 03 Record類中 ToString () 上(shàng)的(de)密封修飾符 記錄類也(yě)得到(dào)了(le/liǎo)改進。從 C# 10 開始,ToString() 方法可以(yǐ)包含 seal 修飾符,這(zhè)會阻止編譯器爲(wéi / wèi)任何派生記錄合成 ToString 實現。 在(zài)本文中的(de)記錄中了(le/liǎo)解有關 ToString () 的(de)更多信息。 有關 ToString () 的(de)更多信息 https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display 04 結構和(hé / huò)匿名類型的(de)表達式 C# 10 支持所有結構的(de) with 表達式,包括記錄結構,以(yǐ)及匿名類型: 這(zhè)将返回一(yī / yì /yí)個(gè)具有新值的(de)新實例。您可以(yǐ)更新任意數量的(de)值。您未設置的(de)值将保留與初始實例相同的(de)值。 ttps://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display 内插字符串改進 當我們在(zài) C# 中添加内插字符串時(shí),我們總覺得在(zài)性能和(hé / huò)表現力方面,使用該語法可以(yǐ)做更多事情。 01 内插字符串處理程序 今天,編譯器将内插字符串轉換爲(wéi / wèi)對 string.Format 的(de)調用。這(zhè)會導緻很多分配——參數的(de)裝箱、參數數組的(de)分配,當然還有結果字符串本身。此外,它在(zài)實際插值的(de)含義上(shàng)沒有任何回旋餘地(dì / de)。 在(zài) C# 10 中,我們添加了(le/liǎo)一(yī / yì /yí)個(gè)庫模式,允許 API “接管”對内插字符串參數表達式的(de)處理。例如,考慮 StringBuilder.Append: 到(dào)目前爲(wéi / wèi)止,這(zhè)将使用新分配和(hé / huò)計算的(de)字符串調用 Append(string? value) 重載,将其附加到(dào) StringBuilder 的(de)一(yī / yì /yí)個(gè)塊中。但是(shì),Append 現在(zài)有一(yī / yì /yí)個(gè)新的(de)重載 Append(refStringBuilder.AppendInterpolatedStringHandler handler),當使用内插字符串作爲(wéi / wèi)參數時(shí),它優先于(yú)字符串重載。 通常,當您看到(dào) SomethingInterpolatedStringHandler 形式的(de)參數類型時(shí),API 作者在(zài)幕後做了(le/liǎo)一(yī / yì /yí)些工作,以(yǐ)更恰當地(dì / de)處理插值字符串以(yǐ)滿足其目的(de)。在(zài)我們的(de) Append 示例中,字符串 “Hello”、args[0] 和(hé / huò)“,how are you?” 将單獨附加到(dào) StringBuilder 中,這(zhè)樣效率更高且結果相同。 有時(shí)您隻想在(zài)特定條件下完成構建字符串的(de)工作。一(yī / yì /yí)個(gè)例子(zǐ)是(shì) Debug.Assert: 在(zài)大(dà)多數情況下,條件爲(wéi / wèi)真,第二個(gè)參數未使用。但是(shì),每次調用都會計算所有參數,從而(ér)不(bù)必要(yào / yāo)地(dì / de)減慢執行速度。Debug.Assert 現在(zài)有一(yī / yì /yí)個(gè)帶有自定義插值字符串構建器的(de)重載,它确保第二個(gè)參數甚至不(bù)被評估,除非條件爲(wéi / wèi)假。 最後,這(zhè)是(shì)一(yī / yì /yí)個(gè)在(zài)給定調用中實際更改字符串插值行爲(wéi / wèi)的(de)示例:String.Create() 允許您指定 IFormatProvider 用于(yú)格式化插值字符串參數本身的(de)洞中的(de)表達式: 你可以(yǐ)在(zài)本文和(hé / huò)有關創建自定義處理程序的(de)本教程中了(le/liǎo)解有關内插字符串處理程序的(de)更多信息。 創建自定義處理程序 内插字符串處理程序的(de)更多信息 https://docs.microsoft.com/dotnet/csharp/whats-new/tutorials/interpolated-string-handler 02 常量内插字符串 如果内插字符串的(de)所有洞都是(shì)常量字符串,那麽生成的(de)字符串現在(zài)也(yě)是(shì)常量。這(zhè)使您可以(yǐ)在(zài)更多地(dì / de)方使用字符串插值語法,例如屬性: 請注意,必須用常量字符串填充洞。其他(tā)類型,如數字或日期值,不(bù)能使用,因爲(wéi / wèi)它們對文化敏感,并且不(bù)能在(zài)編譯時(shí)計算。 其他(tā)改進 C# 10 對整個(gè)語言進行了(le/liǎo)許多較小的(de)改進。其中一(yī / yì /yí)些隻是(shì)使 C# 以(yǐ)您期望的(de)方式工作。 在(zài)解構中混合聲明和(hé / huò)變量 在(zài) C# 10 之(zhī)前,解構要(yào / yāo)求所有變量都是(shì)新的(de),或者所有變量都必須事先聲明。在(zài) C# 10 中,您可以(yǐ)混合: 在(zài)有關解構的(de)文章中了(le/liǎo)解更多信息。 改進的(de)明确分配 如果您使用尚未明确分配的(de)值,C# 會産生錯誤。C# 10 可以(yǐ)更好地(dì / de)理解您的(de)代碼并且産生更少的(de)虛假錯誤。這(zhè)些相同的(de)改進還意味着您将看到(dào)更少的(de)針對空引用的(de)虛假錯誤和(hé / huò)警告。 在(zài) C# 10 中的(de)新增功能文章中了(le/liǎo)解有關 C# 确定賦值的(de)更多信息。 擴展的(de)屬性模式 C# 10 添加了(le/liǎo)擴展屬性模式,以(yǐ)便更輕松地(dì / de)訪問模式中的(de)嵌套屬性值。例如,如果我們在(zài)上(shàng)面的(de) Person 記錄中添加一(yī / yì /yí)個(gè)地(dì / de)址,我們可以(yǐ)通過以(yǐ)下兩種方式進行模式匹配: 擴展屬性模式簡化了(le/liǎo)代碼并使其更易于(yú)閱讀,尤其是(shì)在(zài)匹配多個(gè)屬性時(shí)。 在(zài)模式匹配文章中了(le/liǎo)解有關擴展屬性模式的(de)更多信息。 模式匹配文章 https://docs.microsoft.com/dotnet/csharp/languagereference/operators/patterns#property-pattern 調用者表達式屬性 CallerArgumentExpressionAttribute 提供有關方法調用上(shàng)下文的(de)信息。與其他(tā) CompilerServices 屬性一(yī / yì /yí)樣,此屬性應用于(yú)可選參數。在(zài)這(zhè)種情況下,一(yī / yì /yí)個(gè)字符串: 傳遞給 CallerArgumentExpression 的(de)參數名稱是(shì)不(bù)同參數的(de)名稱。作爲(wéi / wèi)參數傳遞給該參數的(de)表達式将包含在(zài)字符串中。例如, ArgumentNullException.ThrowIfNull() 是(shì)如何使用此屬性的(de)一(yī / yì /yí)個(gè)很好的(de)示例。它通過默認提供的(de)值來(lái)避免必須傳入參數名稱:global using System;
global using static System.Console;
global using Env = System.Environment;<PropertyGroup>
<!-- Other properties like OutputType and TargetFramework -->
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup><ItemGroup>
<Using Remove="System.Threading.Tasks" />
</ItemGroup><ItemGroup>
<Using Include="System.IO.Pipes" />
</ItemGroup>namespace MyCompany.MyNamespace;
class MyClass // Note: no indentation
{ ... } Func<string, int> parse = (string s) => int.Parse(s);
var parse = (string s) => int.Parse(s);
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>Func<int> read = Console.Read;
Action<string> write = Console.Write;var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choosevar choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";public struct Address
{
public Address()
{
City = "<unknown>";
}
public string City { get; init; }
}public struct Address
{
public string City { get; init; } = "<unknown>";
}public record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}public record struct Person(string FirstName, string LastName);
var person = new Person { FirstName = "Mads", LastName = "Torgersen"};
public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}var person2 = person with { LastName = "Kristensen" };
在(zài)本文中了(le/liǎo)解有關 with 的(de)更多信息var sb = new StringBuilder();
sb.Append($"Hello {args[0]}, how are you?");Debug.Assert(condition, $"{SomethingExpensiveHappensHere()}");
String.Create(CultureInfo.InvariantCulture, $"The result is {result}");
https://docs.microsoft.com/dotnet/csharp/languagereference/tokens/interpolated#compilation-of-interpolated-strings[Obsolete($"Call {nameof(Discard)} instead")]
int x2;
int y2;
(x2, y2) = (0, 1); // Works in C# 9
(var x, var y) = (0, 1); // Works in C# 9
(x2, var y3) = (0, 1); // Works in C# 10 onwards object obj = new Person
{
FirstName = "Kathleen",
LastName = "Dollard",
Address = new Address { City = "Seattle" }
};
if (obj is Person { Address: { City: "Seattle" } })
Console.WriteLine("Seattle");
if (obj is Person { Address.City: "Seattle" }) // Extended property pattern
Console.WriteLine("Seattle");void CheckExpression(bool condition,
[CallerArgumentExpression("condition")] string? message = null )
{
Console.WriteLine($"Condition: {message}");
}var a = 6;
var b = true;
CheckExpression(true);
CheckExpression(b);
CheckExpression(a > 5);
// Output:
// Condition: true
// Condition: b
// Condition: a > 5void MyMethod(object value)
{
ArgumentNullException.ThrowIfNull(value);
}