【譯】C# 11 特性的早期預覽

MingsonZheng發表於2022-03-06

原文 | Kathleen

翻譯 | 鄭子銘

Visual Studio 17.1(Visual Studio 2022 Update 1)和 .NET SDK 6.0.200 包含 C# 11 的預覽功能!您可以更新 Visual Studio 或下載最新的 .NET SDK 來獲得這些功能。

檢視帖子 Visual Studio 2022 17.1 現已推出!瞭解 Visual Studio 中的新增功能和釋出 .NET 7 Preview 1 的帖子以瞭解更多 .NET 7 預覽功能。

設計 C# 11

我們喜歡公開設計和開發!您可以在 CSharpLang 儲存庫中找到有關未來 C# 功能的建議和語言設計會議的註釋。主頁解釋了我們的設計過程,您可以在 .NET Community Runtime and Languages Standup 上收聽 Mads Torgersen,他在其中談到了設計過程。

一旦計劃好某個功能的工作,工作和跟蹤就會轉移到 Roslyn 儲存庫。您可以在功能狀態頁面上找到即將推出的功能的狀態。您可以看到我們正在進行的工作以及合併到每個預覽中的內容。您還可以回顧以前的版本以檢查您可能忽略的功能。

在這篇文章中,我將這些有時是複雜的技術性討論提煉成程式碼中每個特性的含義。

我們希望您能試用這些新的預覽功能,並讓我們知道您的想法。要試用 C# 11 預覽功能,請建立一個 C# 專案並將 LangVersion 設定為 Preview。您的 .csproj 檔案可能如下所示:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>
</Project>

C# 11 預覽:允許在插值字串的“孔”中換行

在提案中閱讀有關此更改的更多資訊,刪除非逐字插值字串中的插值不能包含換行符的限制。 #4935

C# 支援兩種型別的內插字串:逐字和非逐字內插字串(分別為 $@"" 和 $"")。它們之間的一個關鍵區別是非逐字插值字串不能在其文字段中包含換行符,而必須使用轉義符(如 \r\n)。逐字插值字串可以在其文字段中包含換行符,並且不會轉義換行符或其他字元(除了“”來轉義引號本身)。所有這些行為保持不變。

以前,這些限制擴充套件到非逐字插值字串的孔。孔是表示插值表示式的簡寫方式,是花括號內提供執行時值的部分。孔本身不是文字,不應遵守內插字串文字段的轉義/換行規則。

例如,以下內容會導致 C# 10 中的編譯器錯誤,並且在此 C# 11 預覽版中是合法的:

var v = $"Count ist: { this.Is.Really.Something()
                            .That.I.Should(
                                be + able)[
                                    to.Wrap()] }.";

C# 11 預覽:列表模式

閱讀更多關於提案列表模式中的這種變化。

新的列表模式允許您匹配列表和陣列。您可以匹配元素,並且可以選擇包含匹配零個或多個元素的切片模式。使用切片模式,您可以丟棄或捕獲零個或多個元素。

列表模式的語法是方括號括起來的值,切片模式是兩個點。切片模式後面可以跟另一個列表模式,例如 var 模式來捕獲切片的內容。

模式 [1, 2, .., 10] 匹配以下所有內容:

int[] arr1 = { 1, 2, 10 };
int[] arr1 = { 1, 2, 5, 10 };
int[] arr1 = { 1, 2, 5, 6, 7, 8, 9, 10 };

要探索列表模式,請考慮:

public static int CheckSwitch(int[] values)
    => values switch
    {
        [1, 2, .., 10] => 1,
        [1, 2] => 2,
        [1, _] => 3,
        [1, ..] => 4,
        [..] => 50
    };

當它傳遞以下陣列時,結果如下所示:

WriteLine(CheckSwitch(new[] { 1, 2, 10 }));          // prints 1
WriteLine(CheckSwitch(new[] { 1, 2, 7, 3, 3, 10 })); // prints 1
WriteLine(CheckSwitch(new[] { 1, 2 }));              // prints 2
WriteLine(CheckSwitch(new[] { 1, 3 }));              // prints 3
WriteLine(CheckSwitch(new[] { 1, 3, 5 }));           // prints 4
WriteLine(CheckSwitch(new[] { 2, 5, 6, 7 }));        // prints 50

您還可以捕獲切片模式的結果:

public static string CaptureSlice(int[] values)
    => values switch
    {
        [1, .. var middle, _] => $"Middle {String.Join(", ", middle)}",
        [.. var all] => $"All {String.Join(", ", all)}"
    };

列表模式適用於任何可數和可索引的型別——這意味著它具有可訪問的 Length 或 Count 屬性,並且具有 int 或 System.Index 引數的索引器。切片模式適用於任何可數和可切片的型別——這意味著它具有一個可訪問的索引器,該索引器將 Range 作為引數,或者具有一個具有兩個 int 引數的可訪問的 Slice 方法。

我們正在考慮在 IEnumerable 型別上新增對列表模式的支援。如果您有機會使用此功能,請告訴我們您對此的想法。

C# 11 預覽:引數空值檢查

在提案引數空檢查中閱讀有關此更改的更多資訊。

我們將此功能放入此早期預覽版中,以確保我們有時間獲得反饋。已經討論過一種非常簡潔的語法與一種更冗長的語法。我們希望獲得客戶反饋以及有機會嘗試此功能的使用者。

使用樣板程式碼的變體來驗證方法引數是否為空是很常見的,例如:

public static void M(string s)
{
    if (s is null)
    {
        throw new ArgumentNullException(nameof(s));
    }
    // Body of the method
}

使用引數空檢查,您可以通過新增 !! 到引數名稱來縮寫您的意圖:

public static void M(string s!!)
{
    // Body of the method
}

將生成程式碼以執行空值檢查。生成的空值檢查將在方法中的任何程式碼之前執行。對於建構函式,空值檢查發生在欄位初始化、呼叫基建構函式和呼叫 this 建構函式之前。

此功能獨立於可空引用型別 (NRT),儘管它們可以很好地協同工作。 NRT 可幫助您在設計時瞭解 null 是否可能。引數空值檢查可以更輕鬆地在執行時檢查空值是否已傳遞給您的程式碼。當您的程式碼與可能未啟用 NRT 的外部程式碼互動時,這一點尤其重要。

檢查和 if (param is null) throw new ArgumentNullException(...) 是等效的。當多個引數包含 !! 運算子,則檢查將按照宣告引數的順序進行。

下面是一些 !! 在哪裡可以使用的限制規則:

  • 只有在有實現時才能將空檢查應用於引數。例如,抽象方法引數不能使用 !!。其他不能使用的情況包括:
    • 外部方法引數。
    • 委託引數。
    • 當方法不是預設介面方法 (DIM) 時的介面方法引數。
  • 空值檢查只能應用於可以檢查的引數。

根據第二條規則排除的場景示例是丟棄和輸出引數。可以對 ref 和 in 引數進行空值檢查。

允許對索引器引數進行空檢查,並將檢查新增到 get 和 set 訪問器。例如:

public string this[string key!!] { get { ... } set { ... } }

Null-checks 可以用於 lambda 引數,無論它們是否被括號括起來:

// An identity lambda which throws on a null input
Func<string, string> s = x!! => x;

非同步方法可以有空檢查引數。呼叫方法時會發生空值檢查。

該語法對迭代器方法的引數也有效。呼叫迭代器方法時會發生空值檢查,而不是遍歷底層列舉器時。這適用於傳統或非同步迭代器:

class Iterators {
    IEnumerable<char> GetCharacters(string s!!) {
        foreach (var c in s) {
            yield return c;
        }
    }

    void Use() {
        // The invocation of GetCharacters will throw
        IEnumerable<char> e = GetCharacters(null);
    }
}

與可空引用型別的互動

任何具有 !! 的引數應用於其名稱的運算子將以可空狀態為非空開始。即使引數本身的型別可能為 null,也是如此。這可能發生在顯式可為空的型別(例如字串?)或不受約束的型別引數中。

當 !!引數上的語法與引數上的顯式可空型別相結合,編譯器將發出警告:

void WarnCase<T>(
    string? name!!,     // CS8995   Nullable type 'string?' is null-checked and will throw if null. 
    T value1!!        // Okay
)

建構函式

當您從程式碼中的顯式空檢查更改為使用空驗證語法 (!!) 進行空檢查時,會有一個很小但可以觀察到的變化。您的顯式驗證發生在使用 this 呼叫的欄位初始值設定項、基類建構函式和建構函式之後。使用引數空檢查語法執行的空檢查將在任何這些執行之前發生。早期的測試人員發現這個順序很有幫助,我們認為這種差異很少會對程式碼產生不利影響。但在從顯式空檢查轉移到新語法之前,請檢查它是否不會影響您的程式。

設計注意事項

您可以聽到 Jared Parsons 在 2022 年 2 月 9 日的 .NET 語言和執行時社群站會中的演講。當 Jared 加入我們的行列時,該剪輯開始了大約 45 分鐘,更多地討論了將這個功能引入預覽的決定,並做出了回應一些常見的反饋。

有些人在看到 PR 在 .NET 執行時使用此功能時瞭解了此功能。 Microsoft 的其他團隊提供了有關 C# 的重要 dogfooding 反饋。得知 .NET 執行時使用這種新的空檢查語法刪除了近 20,000 行程式碼,這令人興奮。

在引數名稱上的語法是 !!。它在名稱上,而不是型別上,因為這是在您的程式碼中如何處理該特定引數的一個特徵。我們決定不使用屬性是因為它會如何影響程式碼的可讀性,並且因為屬性很少會像此功能那樣影響程式的執行方式。

我們考慮並拒絕了對所有可空引數進行空檢查的全域性設定。引數空值檢查強制設計選擇如何處理空值。有許多方法,其中 null 引數是有效值。在型別不為 null 的任何地方都這樣做會過度,並且會對效能產生影響。僅限制於易受 null 影響的方法(例如公共介面)將是極其困難的。我們還從 .NET 執行時工作中瞭解到,有很多地方不適合進行檢查,因此需要按引數選擇退出機制。我們目前認為執行時空值檢查的全域性方法可能不合適,如果我們考慮使用全域性方法,那將是一個不同的特性。

總結

Visual Studio 17.1 和 .NET SDK 6.0.200 提供了對 C# 11 的早期瞭解。您可以在插值字串的花括號(孔)內使用引數空檢查、列表模式和新行。

我們希望您通過更新 Visual Studio 或下載最新的 .NET SDK,然後將 LangVersion 設定為預覽來檢視 C# 11 預覽功能。

我們期待聽到您的想法,在這裡或通過 GitHub 上的 CSharpLang 儲存庫中的討論

原文連結

Early peek at C# 11 features

知識共享許可協議

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含連結: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。

如有任何疑問,請與我聯絡 (MingsonZheng@outlook.com) 。

相關文章