天天看點

微軟正式釋出C#10

IT之家 2 月 12 日消息,據微軟中國 MSDN,宣布 C# 10 作為 .NET 6 和 Visual Studio 2022 的一部分已經釋出了。在這篇文章中,微軟将介紹 C# 10 的許多新功能,這些功能使你的代碼更漂亮、更具表現力、更快。

微軟正式釋出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 适用于整個項目:

global using System;

你可以在全局 using 指令中使用 using 的任何功能。例如,添加靜态導入類型并使該類型的成員和嵌套類型在整個項目中可用。如果你在 using 指令中使用别名,該别名也會影響你的整個項目:

global using static System.Console;global using Env = System.Environment;

你可以将全局使用放在任何 .cs 檔案中,包括 Program.cs 或專門命名的檔案,如 globalusings.cs。全局 usings 的範圍是目前編譯,一般對應目前項目。

有關詳細資訊,請參閱全局 using 指令。

https://docs.microsoft.com/dotnet/csharp/languagereference/keywords/using-directive#global-modifier

隐式 usings

隐式 usings 功能會自動為你正在建構的項目類型添加通用的全局 using 指令。要啟用隐式 usings,請在 .csproj 檔案中設定 ImplicitUsings 屬性:

enable

在新的 .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

https://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives

Combining using 功能

檔案頂部的傳統 using 指令、全局 using 指令和隐式 using 可以很好地協同工作。隐式 using 允許你在項目檔案中包含适合你正在建構的項目類型的 .NET 命名空間。全局 using 指令允許你包含其他命名空間,以使它們在整個項目中可用。代碼檔案頂部的 using 指令允許你包含項目中僅少數檔案使用的命名空間。

無論它們是如何定義的,額外的 using 指令都會增加名稱解析中出現歧義的可能性。如果遇到這種情況,請考慮添加别名或減少要導入的命名空間的數量。例如,你可以将全局 using 指令替換為檔案子集頂部的顯式 using 指令。

如果你需要删除通過隐式 usings 包含的命名空間,你可以在項目檔案中指定它們:

你還可以添加命名空間,就像它們是全局 using 指令一樣,你可以将 Using 項添加到項目檔案中,例如:

檔案範圍的命名空間

許多檔案包含單個命名空間的代碼。從 C# 10 開始,你可以将命名空間作為語句包含在内,後跟分号且不帶花括号:

namespace MyCompany.MyNamespace;class MyClass // Note: no indentation{ ... }

他簡化了代碼并删除了嵌套級别。隻允許一個檔案範圍的命名空間聲明,并且它必須在聲明任何類型之前出現。

有關檔案範圍命名空間的更多資訊,請參閱命名空間關鍵字文章。

命名空間關鍵字文章 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.NET Minimal API

https://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/

lambda 的自然類型

Lambda 表達式現在有時具有“自然”類型。這意味着編譯器通常可以推斷出 lambda 表達式的類型。

到目前為止,必須将 lambda 表達式轉換為委托或表達式類型。在大多數情況下,你會在 BCL 中使用重載的 Func 或 Action 委托類型之一:

Funcparse = (string s) => int.Parse(s);

但是,從 C# 10 開始,如果 lambda 沒有這樣的“目标類型”,微軟将嘗試為你計算一個:

var parse = (string s) => int.Parse(s);

你可以在你最喜歡的編輯器中将滑鼠懸停在 var parse 上,然後檢視類型仍然是 Func。一般來說,編譯器将使用可用的 Func 或 Action 委托(如果存在合适的委托)。否則,它将合成一個委托類型(例如,當你有 ref 參數或有大量參數時)。

并非所有 lambda 表達式都有自然類型 —— 有些隻是沒有足夠的類型資訊。例如,放棄參數類型将使編譯器無法決定使用哪種委托類型:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

lambda 的自然類型意味着它們可以配置設定給較弱的類型,例如 object 或 Delegate:

object parse = (string s) => int.Parse(s); // FuncDelegate parse = (string s) => int.Parse(s); // Func

當涉及到表達式樹時,微軟結合了“目标”和“自然”類型。如果目标類型是 LambdaExpression 或非泛型 Expression(所有表達式樹的基類型)并且 lambda 具有自然委托類型 D,微軟将改為生成 Expression:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression>Expression parseExpr = (string s) => int.Parse(s); // Expression>

方法組的自然類型

方法組(即沒有參數清單的方法名稱)現在有時也具有自然類型。你始終能夠将方法組轉換為相容的委托類型:

Funcread = Console.Read;Actionwrite = Console.Write;

現在,如果方法組隻有一個重載,它将具有自然類型:

var read = Console.Read; // Just one overload; Funcinferredvar write = Console.Write; // ERROR: Multiple overloads, can't choose

lambda 的傳回類型

在前面的示例中,lambda 表達式的傳回類型是顯而易見的,并被推斷出來的。情況并非總是如此:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

在 C# 10 中,你可以在 lambda 表達式上指定顯式傳回類型,就像在方法或本地函數上一樣。傳回類型在參數之前。當你指定一個顯式的傳回類型時,參數必須用括号括起來,這樣編譯器或其他開發人員不會太混淆:

var choose = object (bool b) => b ? 1 : "two"; // Func

lambda 上的屬性

從 C# 10 開始,你可以将屬性放在 lambda 表達式上,就像對方法和本地函數一樣。當有屬性時,lambda 的參數清單必須用括号括起來:

Funcparse = [Example(1)] (s) => int.Parse(s);var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

就像本地函數一樣,如果屬性在 AttributeTargets.Method 上有效,則可以将屬性應用于 lambda。

Lambda 的調用方式與方法和本地函數不同,是以在調用 lambda 時屬性沒有任何影響。但是,lambdas 上的屬性對于代碼分析仍然有用,并且可以通過反射發現它們。

structs 的改進

C# 10 為 structs 引入了功能,可在 structs (結構) 和類之間提供更好的奇偶性。這些新功能包括無參數構造函數、字段初始值設定項、記錄結構和 with 表達式。

01 無參數結構構造函數和字段初始值設定項

在 C# 10 之前,每個結構都有一個隐式的公共無參數構造函數,該構造函數将結構的字段設定為預設值。在結構上建立無參數構造函數是錯誤的。

從 C# 10 開始,你可以包含自己的無參數結構構造函數。如果你不提供,則将提供隐式無參數構造函數以将所有字段設定為預設值。你在結構中建立的無參數構造函數必須是公共的并且不能是部分的:

public struct Address{ public Address() { City = ""; } public string City { get; init; }}

你可以如上所述在無參數構造函數中初始化字段,也可以通過字段或屬性初始化程式初始化它們:

public struct Address{ public string City { get; init; } = "";}

通過預設建立或作為數組配置設定的一部分建立的結構會忽略顯式無參數構造函數,并始終将結構成員設定為其預設值。有關結構中無參數構造函數的更多資訊,請參閱結構類型。

02 Record structs

從 C# 10 開始,現在可以使用 record struct 定義 record。這些類似于 C# 9 中引入的 record 類:

public record struct Person{ public string FirstName { get; init; } public string LastName { get; init; }}

你可以繼續使用 record 定義記錄類,也可以使用 record 類來清楚地說明。

結構已經具有值相等 —— 當你比較它們時,它是按值。記錄結構添加 IEquatable 支援和 == 運算符。記錄結構提供 IEquatable 的自定義實作以避免反射的性能問題,并且它們包括記錄功能,如 ToString () 覆寫。

記錄結構可以是位置的,主構造函數隐式聲明公共成員:

public record struct Person(string FirstName, string LastName);

主構造函數的參數成為記錄結構的公共自動實作屬性。與 record 類不同,隐式建立的屬性是讀 / 寫的。這使得将元組轉換為命名類型變得更加容易。将傳回類型從 (string FirstName, string LastName) 之類的元組更改為 Person 的命名類型可以清理你的代碼并保證成員名稱一緻。聲明位置記錄結構很容易并保持可變語義。

如果你聲明一個與主要構造函數參數同名的屬性或字段,則不會合成任何自動屬性并使用你的。

要建立不可變的記錄結構,請将 readonly 添加到結構(就像你可以添加到任何結構一樣)或将 readonly 應用于單個屬性。對象初始化器是可以設定隻讀屬性的構造階段的一部分。這隻是使用不可變記錄結構的一種方法:

var person = new Person { FirstName = "Mads", LastName = "Torgersen"};public readonly record struct Person{ public string FirstName { get; init; } public string LastName { 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 表達式,包括記錄結構,以及匿名類型:

var person2 = person with { LastName = "Kristensen" };

這将傳回一個具有新值的新執行個體。你可以更新任意數量的值。你未設定的值将保留與初始執行個體相同的值。

在本文中了解有關 with 的更多資訊

了解有關 with 的更多資訊

内插字元串改進

當微軟在 C# 中添加内插字元串時,微軟總覺得在性能和表現力方面,使用該文法可以做更多事情。

01 内插字元串處理程式

今天,編譯器将内插字元串轉換為對 string.Format 的調用。這會導緻很多配置設定 —— 參數的裝箱、參數數組的配置設定,當然還有結果字元串本身。此外,它在實際插值的含義上沒有任何回旋餘地。

在 C# 10 中,微軟添加了一個庫模式,允許 API“接管”對内插字元串參數表達式的處理。例如,考慮 StringBuilder.Append:

var sb = new StringBuilder();sb.Append($"Hello {args[0]}, how are you?");

到目前為止,這将使用新配置設定和計算的字元串調用 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, $"The result is {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 中,你可以混合:

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

在有關解構的文章中了解更多資訊。

改進的明确配置設定

如果你使用尚未明确配置設定的值,C# 會産生錯誤。C# 10 可以更好地了解你的代碼并且産生更少的虛假錯誤。這些相同的改進還意味着你将看到更少的針對空引用的虛假錯誤和警告。

在 C# 10 中的新增功能文章中了解有關 C# 确定指派的更多資訊。

C# 10 中的新增功能文章

https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#improved-definite-assignment

擴充的屬性模式

C# 10 添加了擴充屬性模式,以便更輕松地通路模式中的嵌套屬性值。例如,如果微軟在上面的 Person 記錄中添加一個位址,微軟可以通過以下兩種方式進行模式比對:

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");

擴充屬性模式簡化了代碼并使其更易于閱讀,尤其是在比對多個屬性時。

在模式比對文章中了解有關擴充屬性模式的更多資訊。

模式比對文章

https://docs.microsoft.com/dotnet/csharp/languagereference/operators/patterns#property-pattern

調用者表達式屬性

CallerArgumentExpressionAttribute 提供有關方法調用上下文的資訊。與其他 CompilerServices 屬性一樣,此屬性應用于可選參數。在這種情況下,一個字元串:

void CheckExpression(bool condition, [CallerArgumentExpression("condition")] string? message = null ){ Console.WriteLine($"Condition: {message}");}

傳遞給 CallerArgumentExpression 的參數名稱是不同參數的名稱。作為參數傳遞給該參數的表達式将包含在字元串中。例如,

var a = 6;var b = true;CheckExpression(true);CheckExpression(b);CheckExpression(a > 5);// Output:// Condition: true// Condition: b// Condition: a > 5

ArgumentNullException.ThrowIfNull () 是如何使用此屬性的一個很好的示例。它通過預設提供的值來避免必須傳入參數名稱:

void MyMethod(object value){ ArgumentNullException.ThrowIfNull(value);}

了解有關 CallerArgumentExpressionAttribute 的更多資訊

https://docs.microsoft.com/dotnet/csharp/languagereference/attributes/caller-information#argument-expressions

繼續閱讀