預覽 C# 10 的新東西

預覽 C# 10 的新東西

學習永不止步

最後更新 2021/6/1 下午10:19
Ken Bonny&Rwing
預計閱讀 6 分鐘
分類
.NET
標籤
.NET C#

本週早些時候(譯注:原文發表於 5 月 1 日),我關注了 Mads TorgersenDotNet SouthWest 大會上的演講,他是微軟的 C# 語言的首席設計師。他概述了 C# 10 即將包含的很酷的一些新東西。讓我們來快速瀏覽一下。

小小的免責聲明,這些變化中的大部分已經基本完成。但是由於它仍在積極的開發中,我不能保證 C# 10 發佈時所有東西都會完全如實。

struct record

他談到的第一件事是,目前 record 的實現是使用一個 class 作為基礎對象的。現在還會有一個 record struct 的變體可用,所以基礎類型可以是一個值類型。區別在於,普通的 record 在函數之間傳遞的是參考,而 record struct 是其值的拷貝。 record struct 也會支援 with 運算符。

同時,還允許向 record 添加運算符,兩種 record 類型都可以。

record Person(string Name, string Email)
{
  public static Person operator +(Person first, Person second)
  {
    // logic goes here
  }
}

required

C# 團隊關注的目標之一是使對象的初始化變得更容易。這就是為什麼可以對 class、struct、record 或 struct record 的屬性添加 required 標記 。它使得這些屬性必須填寫。這可以透過建構函式來完成,也可以透過對象初始化來完成。下面的兩個類的定義是等價的。如果你添加了 required 關鍵字,那麼就無法在不設定 Name 屬性的情況下將 Person 實例化。編譯器會拋出錯誤,無法編譯。

class Person
{
  public required string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
}
class Person
{
  public Person(string name) => Name = name;

  public string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
}

field

為了進一步的改善屬性,將允許完全擺脫 backing field 。新的關鍵字 field 將提供對上述字段的存取。對 setter 和 init only 屬性都可以使用。

class Person
{
  public string Name { get; init => field = value.Trim(); }
  public DateTime DateOfBirth { get; set => field = value.Date; }
}

with

在下一個版本中還會有許多有趣的小改進。其實中一個是匿名類型也將支援 with 運算符。

var foo = new
{
  Name = "Foo",
  Email = "foo@mail.com"
};
var bar = foo with {Name = "Bar"};

namespace

現在可以創建一個帶有命名空間匯入的檔案,然後在任何地方都可以使用這個匯入。例如,如果有一個很常用的命名空間,幾乎在每個檔案中都有使用例如 Microsoft.Extensions.Logging.ILogger ,那麼就可以在任何.cs 檔案(我建議在 Program.cs 或專門的 Imports.cs )中添加一行 global using Microsoft.Extensions.Logging.ILogger,之後這個命名空間將可以在整個專案中使用。注意,這不適用於整個解決方案! 沒有人能夠預測哪些地方需要匯入,所以它們被分組到每個專案中。

// 譯注:原文並沒有提供程式碼範例,為了更好方便大家理解私自添加了一個演示
// Program.cs 檔案
global using System;

// Sample.cs 檔案
// 可以不用再using System;
Console.WriteLine("foo");

隨後,還將對命名空間也會有一個最佳化。現在命名空間需要大括號 來包起來程式碼,這就意味著所有程式碼至少要縮排一次。為了節省 tab(或四個空白)和螢幕空間,在檔案的任何地方添加一個命名空間,將使所有程式碼都屬於該命名空間。有研究表明絕大多數情況下,一個檔案中所有的程式碼都屬於同一個命名空間。使用這個方案後,檔案大小隨之減少,這對一個解決方案來說可能並不明顯(即使它包含成千上萬的檔案),但在 GitHub/GitLab/BitBucket/... 的規模上,我認為這將為他們節省一些空間。如果有人仍想在一個檔案中包含多個命名空間,使用大括號的選項仍然可用。

// 傳統的方式 LegacyNamespace.cs
namespace LegacyNamespace
{
  class Foo
  {
    // legacy code goes here
  }
}

// 簡化的方式 SimplifiedNamespace.cs
namespace SimplifiedNamespace;
class Bar
{
  // awesome code goes here
}

lambda

對 lambda 陳述式也會有一些很酷的更新。編譯器將對推斷 lambda 簽章提供更好的支援,而且還可以添加屬性。話可以顯式指定返回類型,以幫助編譯器理解 lambda。

var f = Console.WriteLine;
var f = x => x; // 推斷返回類型
var f = (string x) => x; // 推斷簽章
var f = [NotNull] x => x; // 添加屬性
var f = [NotNull] (int x) => x;
var f = [return: NotNull] static x => x; // 添加屬性
var f = T () => default; // 顯式返回類型
var f = ref int (ref int x) => ref x; // 使用 ref
var f = int (x) => x; // 顯式指定隱式輸入的返回類型
var f = static void (_) => Console.Write("Help");

感謝 Schooley 提出了一個不那麼容易混淆的屬性例子

interface

最後,將有可能在介面上指定靜態方法和屬性。我知道這將是一個有爭議的話題,就像給介面添加默認實現一樣。我不喜歡它。然而,這可能非常有趣。想像一下,你可以指定一個介面的默認值或指定創建方法。

interface IFoo
{
  static IFoo Empty { get; }
  static operator +(IFoo first, IFoo second);
}
class Foo : IFoo
{
  public static IFoo Empty => new Foo();
  public static operator +(IFoo first, IFoo second) => /* do calculation here */;
}

就個人而言,我喜歡這些變化。我最喜歡的是對命名空間的改變和對介面的改進。總之,未來是光明的 C# 的。嗯嗯... (譯注:這裡作者玩了一個梗,原文 the future is seeing sharp,see sharp 發音類似 C# )

謝謝各位,大家再見。

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀