C# 9.0 新特性預覽 – 類型推導的 new

C# 9.0 新特性預覽 – 類型推導的 new

前言

隨著 .NET 5 發布日期的日益臨近,其對應的 C# 新版本已確定為 C# 9.0,其中新增加的特性(或語法糖)也已基本鎖定,本系列文章將向大家展示它們。

目錄

[C# 9.0 新特性預覽 – 類型推導的 new]
[C# 9.0 新特性預覽 – Lambda 中的棄元]
[C# 9.0 新特性預覽 – 更簡便的空參數檢查]
[C# 9.0 新特性預覽 – Record 類型]
[C# 9.0 新特性預覽 – 模式匹配的改善]
[C# 9.0 新特性預覽 – 其他小的變化]

具有類型推導的 new 表達式 (Target-typed new expressions)

這是一個本應隨著 C# 8.0 發布的語言特性,但因種種原因在發布 C# 8.0 的最後關頭,它被移出了最終的發布版本,下面我們來認識認識它。
大家都知道,C# 在3.0中新增 var 關鍵字來做隱式類型聲明,把繁重的聲明語法簡化了。

Dictionary<string, List<int>> field = new Dictionary<string, List<int>>()
// 可以簡化為
var field = new Dictionary<string, List<int>>()

var 關鍵字的基本原理不再複述,簡單說就是編譯器可以根據等號後面的類型推導出 var 的類型,那麼是不是也可以反過來,我們先聲明類型,接下來的 new 關鍵字後面就不用寫類型了呢?於是本文介紹的特性來了:

Dictionary<string, List<int>> field = new Dictionary<string, List<int>>()
// C# 9.0 中可以寫成
Dictionary<string, List<int>> field = new()

從以上程式碼可以看出語法很簡單,即省略了繁瑣的可以推導出的類型。
其語法 Spec 如下:

'new' '(' argument_list? ')' object_or_collection_initializer?

搭配初始化器,我們可以進一步簡化帶有初始值的初始化。

Dictionary<string, List<int>> field = new() {
    { "item1", new() { 1, 2, 3 } }
};

進一步展示該語法在各種情況下的使用

在所有可以推導出類型的上下文中,都可以使用,例如:

XmlReader.Create(reader, new() { IgnoreWhitespace = true });

帶有參數的構造方法:

class C {
    C(params int[] p) {}
}

C c = new(1, 2, 3);

調用方法時:

class A {}
static void M(A a) {};

M(a: new());

配合對象初始化器:

X x = new() { field = 42 };

泛型的類型推導,需要注意,要有一個顯示類型聲明才能正確推導:

void M<T>(T t1, T t2) {}

M(new X(), new());

類似的,數組的聲明

var arr = new[] {new X(), new()};

不適用此特性的場景

值類型的初始化,可以使用 default 替代

int x = new(); // ERROR
Struct y = new(); // ERROR

使用 as 操作時無法正確推導

Console.Write(new() as X); // ERROR

自然,使用 var 時也無法推導

var x = new(); // ERROR

有歧義的重載

void M(object a, X b) => Console.Write($"{a} {b}");
void M(X a, object b) => Console.Write($"{a} {b}");

M(new(), new()); // ERROR

需要注意的地方

這個新語法 new(),比較容易與匿名類型語法混淆 new{},它們兩個是完全不同的東西,需要注意一下。

參考

[Proposal: Target-typed new expressions]
[Unit test: TargetTypedNewTests.cs]