微軟正式釋出 C# 10,支援.NET 6 和 Visual Studio 2022 (附更新內容大全)
2 月 12 日訊息,據微軟中國 MSDN,宣佈 C# 10 作為 .NET 6 和 Visual Studio 2022 的一部分已經發布了。在這篇文章中,微軟將介紹 C# 10 的許多新功能,這些功能使你的程式碼更漂亮、更具表現力、更快。
閱讀Visual Studio 2022 公告和.NET 6 公告以瞭解更多資訊,包括如何安裝。
Visual Studio 2022公告
https://aka.ms/vs2022gablog
.NET 6
https://aka.ms/dotnet6-GA
全域性和隱式 usings
using 指令簡化了你使用名稱空間的方式。C# 10 包括一個新的全域性 using 指令和隱式 usings,以減少你需要在每個檔案頂部指定的 usings 數量。
全域性 using 指令
如果關鍵字global出現在using指令之前,則using適用於整個專案:
globalusingSystem;
你可以在全域性 using 指令中使用 using 的任何功能。例如,新增靜態匯入型別並使該型別的成員和巢狀型別在整個專案中可用。如果你在 using 指令中使用別名,該別名也會影響你的整個專案:
globalusingstaticSystem.Console;globalusingEnv=System.Environment;
你可以將全域性使用放在任何 .cs 檔案中,包括 Program.cs 或專門命名的檔案,如 globalusings.cs。全域性 usings 的範圍是當前編譯,一般對應當前專案。
有關詳細資訊,請參閱全域性 using 指令。
全域性using指令
https://docs.microsoft.com/dotnet/csharp/languagereference/keywords/using-directive#global-modifier
隱式 usings
隱式 usings 功能會自動為你正在構建的專案型別新增通用的全域性 using 指令。要啟用隱式 usings,請在 .csproj 檔案中設定 ImplicitUsings 屬性:
<PropertyGroup><!--OtherpropertieslikeOutputTypeandTargetFramework--><ImplicitUsings>enable</ImplicitUsings></PropertyGroup>
在新的 .NET 6 模板中啟用了隱式 usings 。在此部落格文章中閱讀有關 .NET 6 模板更改的更多資訊。
一些特定全域性 using 指令集取決於你正在構建的應用程式的型別。例如,控制檯應用程式或類庫的隱式 usings 不同於 ASP.NET 應用程式的隱式 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
Combiningusing 功能
檔案頂部的傳統 using 指令、全域性 using 指令和隱式 using 可以很好地協同工作。隱式 using 允許你在專案檔案中包含適合你正在構建的專案型別的 .NET 名稱空間。全域性 using 指令允許你包含其他名稱空間,以使它們在整個專案中可用。程式碼檔案頂部的 using 指令允許你包含專案中僅少數檔案使用的名稱空間。
無論它們是如何定義的,額外的 using 指令都會增加名稱解析中出現歧義的可能性。如果遇到這種情況,請考慮新增別名或減少要匯入的名稱空間的數量。例如,你可以將全域性 using 指令替換為檔案子集頂部的顯式 using 指令。
如果你需要刪除通過隱式 usings 包含的名稱空間,你可以在專案檔案中指定它們:
<ItemGroup><UsingRemove="System.Threading.Tasks"/></ItemGroup>
你還可以新增名稱空間,就像它們是全域性 using 指令一樣,你可以將 Using 項新增到專案檔案中,例如:
<ItemGroup><UsingInclude="System.IO.Pipes"/></ItemGroup>
檔案範圍的名稱空間
許多檔案包含單個名稱空間的程式碼。從 C# 10 開始,你可以將名稱空間作為語句包含在內,後跟分號且不帶花括號:
namespaceMyCompany.MyNamespace;classMyClass//Note:noindentation{...}
他簡化了程式碼並刪除了巢狀級別。只允許一個檔案範圍的名稱空間宣告,並且它必須在宣告任何型別之前出現。
有關檔案範圍名稱空間的更多資訊,請參閱名稱空間關鍵字文章。
名稱空間關鍵字文章 https://docs.microsoft.com/ dotnet / csharp / languagereference / keywords / namespace
對lambda 表示式和方法組的改進
微軟對 lambda 的語法和型別進行了多項改進。微軟預計這些將廣泛有用,並且驅動方案之一是使 ASP.NET Minimal API 更加簡單。
lambda的語法
https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#lambda-expression-improvements
ASP.NETMinimal API
https://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/
lambda的自然型別
Lambda 表示式現在有時具有“自然”型別。這意味著編譯器通常可以推斷出 lambda 表示式的型別。
到目前為止,必須將 lambda 表示式轉換為委託或表示式型別。在大多數情況下,你會在 BCL 中使用過載的 Func<...> 或 Action<...> 委託型別之一:
Func<string,int>parse=(strings)=>int.Parse(s);
但是,從 C# 10 開始,如果 lambda 沒有這樣的“目標型別”,微軟將嘗試為你計算一個:
varparse=(strings)=>int.Parse(s);
你可以在你最喜歡的編輯器中將滑鼠懸停在 var parse 上,然後檢視型別仍然是 Func<string, int>。一般來說,編譯器將使用可用的 Func 或 Action 委託(如果存在合適的委託)。否則,它將合成一個委託型別(例如,當你有 ref 引數或有大量引數時)。
並非所有 lambda 表示式都有自然型別 —— 有些只是沒有足夠的型別資訊。例如,放棄引數型別將使編譯器無法決定使用哪種委託型別:
varparse=s=>int.Parse(s);//ERROR:Notenoughtypeinfointhelambda
lambda 的自然型別意味著它們可以分配給較弱的型別,例如 object 或 Delegate:
objectparse=(strings)=>int.Parse(s);//Func<string,int>Delegateparse=(strings)=>int.Parse(s);//Func<string,int>
當涉及到表示式樹時,微軟結合了“目標”和“自然”型別。如果目標型別是 LambdaExpression 或非泛型 Expression(所有表示式樹的基型別)並且 lambda 具有自然委託型別 D,微軟將改為生成 Expression<D>:
LambdaExpressionparseExpr=(strings)=>int.Parse(s);//Expression<Func<string,int>>ExpressionparseExpr=(strings)=>int.Parse(s);//Expression<Func<string,int>>
方法組的自然型別
方法組(即沒有引數列表的方法名稱)現在有時也具有自然型別。你始終能夠將方法組轉換為相容的委託型別:
Func<int>read=Console.Read;Action<string>write=Console.Write;
現在,如果方法組只有一個過載,它將具有自然型別:
varread=Console.Read;//Justoneoverload;Func<int>inferredvarwrite=Console.Write;//ERROR:Multipleoverloads,can'tchoose
lambda的返回型別
在前面的示例中,lambda 表示式的返回型別是顯而易見的,並被推斷出來的。情況並非總是如此:
varchoose=(boolb)=>b?1:"two";//ERROR:Can'tinferreturntype
在 C# 10 中,你可以在 lambda 表示式上指定顯式返回型別,就像在方法或本地函式上一樣。返回型別在引數之前。當你指定一個顯式的返回型別時,引數必須用括號括起來,這樣編譯器或其他開發人員不會太混淆:
varchoose=object(boolb)=>b?1:"two";//Func<bool,object>
lambda 上的屬性
從 C# 10 開始,你可以將屬性放在 lambda 表示式上,就像對方法和本地函式一樣。當有屬性時,lambda 的引數列表必須用括號括起來:
Func<string,int>parse=[Example(1)](s)=>int.Parse(s);varchoose=[Example(2)][Example(3)]object(boolb)=>b?1:"two";
就像本地函式一樣,如果屬性在 AttributeTargets.Method 上有效,則可以將屬性應用於 lambda。
Lambda 的呼叫方式與方法和本地函式不同,因此在呼叫 lambda 時屬性沒有任何影響。但是,lambdas 上的屬性對於程式碼分析仍然有用,並且可以通過反射發現它們。
structs的改進
C# 10 為 structs 引入了功能,可在 structs (結構) 和類之間提供更好的奇偶性。這些新功能包括無引數建構函式、欄位初始值設定項、記錄結構和 with 表示式。
01 無引數結構建構函式和欄位初始值設定項
在 C# 10 之前,每個結構都有一個隱式的公共無引數建構函式,該建構函式將結構的欄位設定為預設值。在結構上建立無引數建構函式是錯誤的。
從 C# 10 開始,你可以包含自己的無引數結構建構函式。如果你不提供,則將提供隱式無引數建構函式以將所有欄位設定為預設值。你在結構中建立的無引數建構函式必須是公共的並且不能是部分的:
publicstructAddress{publicAddress(){City="<unknown>";}publicstringCity{get;init;}}
你可以如上所述在無引數建構函式中初始化欄位,也可以通過欄位或屬性初始化程式初始化它們:
publicstructAddress{publicstringCity{get;init;}="<unknown>";}
通過預設建立或作為陣列分配的一部分建立的結構會忽略顯式無引數建構函式,並始終將結構成員設定為其預設值。有關結構中無引數建構函式的更多資訊,請參閱結構型別。
02 Record structs
從 C# 10 開始,現在可以使用 record struct 定義 record。這些類似於 C# 9 中引入的 record 類:
publicrecordstructPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}
你可以繼續使用 record 定義記錄類,也可以使用 record 類來清楚地說明。
結構已經具有值相等 —— 當你比較它們時,它是按值。記錄結構新增 IEquatable<T> 支援和 == 運算子。記錄結構提供 IEquatable<T> 的自定義實現以避免反射的效能問題,並且它們包括記錄功能,如 ToString () 覆蓋。
記錄結構可以是位置的,主建構函式隱式宣告公共成員:
publicrecordstructPerson(stringFirstName,stringLastName);
主建構函式的引數成為記錄結構的公共自動實現屬性。與 record 類不同,隱式建立的屬性是讀 / 寫的。這使得將元組轉換為命名型別變得更加容易。將返回型別從 (string FirstName, string LastName) 之類的元組更改為 Person 的命名型別可以清理你的程式碼並保證成員名稱一致。宣告位置記錄結構很容易並保持可變語義。
如果你宣告一個與主要建構函式引數同名的屬性或欄位,則不會合成任何自動屬性並使用你的。
要建立不可變的記錄結構,請將 readonly 新增到結構(就像你可以新增到任何結構一樣)或將 readonly 應用於單個屬性。物件初始化器是可以設定只讀屬性的構造階段的一部分。這只是使用不可變記錄結構的一種方法:
varperson=newPerson{FirstName="Mads",LastName="Torgersen"};publicreadonlyrecordstructPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}
在本文中瞭解有關記錄結構的更多資訊。
記錄結構
https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record
03 Record 類中 ToString () 上的密封修飾符
記錄類也得到了改進。從 C# 10 開始,ToString () 方法可以包含 seal 修飾符,這會阻止編譯器為任何派生記錄合成 ToString 實現。
在本文中的記錄中瞭解有關 ToString () 的更多資訊。
有關ToString ()的更多資訊
https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display
04 結構和匿名型別的表示式
C# 10 支援所有結構的 with 表示式,包括記錄結構,以及匿名型別:
varperson2=personwith{LastName="Kristensen"};
這將返回一個具有新值的新例項。你可以更新任意數量的值。你未設定的值將保留與初始例項相同的值。
在本文中瞭解有關 with 的更多資訊
瞭解有關with的更多資訊
https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display
內插字串改進
當微軟在 C# 中新增內插字串時,微軟總覺得在效能和表現力方面,使用該語法可以做更多事情。
01 內插字串處理程式
今天,編譯器將內插字串轉換為對 string.Format 的呼叫。這會導致很多分配 —— 引數的裝箱、引數陣列的分配,當然還有結果字串本身。此外,它在實際插值的含義上沒有任何迴旋餘地。
在 C# 10 中,微軟添加了一個庫模式,允許 API“接管”對內插字串引數表示式的處理。例如,考慮 StringBuilder.Append:
varsb=newStringBuilder();sb.Append($"Hello{args[0]},howareyou?");
到目前為止,這將使用新分配和計算的字串呼叫 Append (string? value) 過載,將其附加到 StringBuilder 的一個塊中。但是,Append 現在有一個新的過載 Append (refStringBuilder.AppendInterpolatedStringHandler handler),當使用內插字串作為引數時,它優先於字串過載。
通常,當你看到 SomethingInterpolatedStringHandler 形式的引數型別時,API 作者在幕後做了一些工作,以更恰當地處理插值字串以滿足其目的。在微軟的 Append 示例中,字串“Hello”、args [0] 和“,how are you?”將單獨附加到 StringBuilder 中,這樣效率更高且結果相同。
有時你只想在特定條件下完成構建字串的工作。一個例子是 Debug.Assert:
Debug.Assert(condition,$"{SomethingExpensiveHensHere()}");
在大多數情況下,條件為真,第二個引數未使用。但是,每次呼叫都會計算所有引數,從而不必要地減慢執行速度。Debug.Assert 現在有一個帶有自定義插值字串構建器的過載,它確保第二個引數甚至不被評估,除非條件為假。
最後,這是一個在給定呼叫中實際更改字串插值行為的示例:String.Create () 允許你指定 IFormatProvider 用於格式化插值字串引數本身的洞中的表示式:
String.Create(CultureInfo.InvariantCulture,$"Theresultis{result}");
你可以在本文和有關建立自定義處理程式的本教程中瞭解有關內插字串處理程式的更多資訊。
建立自定義處理程式
https://docs.microsoft.com/dotnet/csharp/languagereference/tokens/interpolated#compilation-of-interpolated-strings
內插字串處理程式的更多資訊
https://docs.microsoft.com/dotnet/csharp/whats-new/tutorials/interpolated-string-handler
02 常量內插字串
如果內插字串的所有洞都是常量字串,那麼生成的字串現在也是常量。這使你可以在更多地方使用字串插值語法,例如屬性:
[Obsolete($"Call{nameof(Discard)}instead")]
請注意,必須用常量字串填充洞。其他型別,如數字或日期值,不能使用,因為它們對文化敏感,並且不能在編譯時計算。
其他改進
C# 10 對整個語言進行了許多較小的改進。其中一些只是使 C# 以你期望的方式工作。
在解構中混合宣告和變數
在 C# 10 之前,解構要求所有變數都是新的,或者所有變數都必須事先宣告。在 C# 10 中,你可以混合:
intx2;inty2;(x2,y2)=(0,1);//WorksinC#9(varx,vary)=(0,1);//WorksinC#9(x2,vary3)=(0,1);//WorksinC#10onwards
在有關解構的文章中瞭解更多資訊。
改進的明確分配
如果你使用尚未明確分配的值,C# 會產生錯誤。C# 10 可以更好地理解你的程式碼並且產生更少的虛假錯誤。這些相同的改進還意味著你將看到更少的針對空引用的虛假錯誤和警告。
在 C# 10 中的新增功能文章中瞭解有關 C# 確定賦值的更多資訊。
C# 10中的新增功能文章
https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#improved-definite-assignment
擴充套件的屬性模式
C# 10 添加了擴充套件屬性模式,以便更輕鬆地訪問模式中的巢狀屬性值。例如,如果微軟在上面的 Person 記錄中新增一個地址,微軟可以通過以下兩種方式進行模式匹配:
objectobj=newPerson{FirstName="Kathleen",LastName="Dollard",Address=newAddress{City="Seattle"}};if(objisPerson{Address:{City:"Seattle"}})Console.WriteLine("Seattle");if(objisPerson{Address.City:"Seattle"})//ExtendedpropertypatternConsole.WriteLine("Seattle");
擴充套件屬性模式簡化了程式碼並使其更易於閱讀,尤其是在匹配多個屬性時。
在模式匹配文章中瞭解有關擴充套件屬性模式的更多資訊。
模式匹配文章
https://docs.microsoft.com/dotnet/csharp/languagereference/operators/patterns#property-pattern
呼叫者表示式屬性
CallerArgumentExpressionAttribute 提供有關方法呼叫上下文的資訊。與其他 CompilerServices 屬性一樣,此屬性應用於可選引數。在這種情況下,一個字串:
voidCheckExpression(boolcondition,[CallerArgumentExpression("condition")]string?message=null){Console.WriteLine($"Condition:{message}");}
傳遞給 CallerArgumentExpression 的引數名稱是不同引數的名稱。作為引數傳遞給該引數的表示式將包含在字串中。例如,
vara=6;varb=true;CheckExpression(true);CheckExpression(b);CheckExpression(a>5);//Output://Condition:true//Condition:b//Condition:a>5
ArgumentNullException.ThrowIfNull ()是如何使用此屬性的一個很好的示例。它通過預設提供的值來避免必須傳入引數名稱:
voidMyMethod(objectvalue){ArgumentNullException.ThrowIfNull(value);}
瞭解有關 CallerArgumentExpressionAttribute 的更多資訊
https://docs.microsoft.com/dotnet/csharp/languagereference/attributes/caller-information#argument-expressions