C# 7 分解式(deconstructor)
文章目錄
C# 7 新增了一種叫做「分解式」(deconstructor)的方法,方法名稱固定為 “Deconstruct”。一旦類別有提供此方法,它就具備了「將物件內的元素逐一分解至多個變數」的能力。
先用一個
沿用上一篇文章的範例,這次只是稍微修改先前的
請看倒數第 3 行,等號的右邊是一個
現在讓我們來看看,如何讓自己設計的類別提供「分解」能力。
先前提過,分解方法的名稱必須是 “Deconstruct”。此方法的回傳型別必須是
如此一來,類別
其中第 2 行完成了件事:宣告兩個
上例的分解式不一定要寫成一行,你也可以將物件分解至事先宣告的變數,像這樣:
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/VKwB83
分解式可以多載
一個類別可以有多個分解式,像這樣:
既然與一般的方法宣告相同,那我們當然也可以直接呼叫它:
分解式可以寫成擴充方法
分解式也能寫成擴充方法,而不一定要寫在類別裡面。同樣延續先前的範例,假設我們無法取得
如果類別本身已經提供了分解式,你又以擴充方法另外寫了相同 signature 的擴充方法 ,此時編譯器會選擇使用類別本身提供的分解式。
OK,現在我們看得很清楚了:`Deconstruct` 方法與一般的 C# 方法沒有太大差異,只是方法名稱得按規定,不能隨便取。這裡要再特別指出的是,實際上會呼叫哪一個分解式,是在編譯時期就決定的,這表示 dynamic 變數無法使用物件的分解式。因此,底下的寫法無法通過編譯:
巢狀分解
C# 7 的分解式還支援巢狀分解。為了解釋這個稍稍複雜一點的語法,這裡仍沿用先前的例子,把
現在
那麼,在應用時,可以這樣寫:
其中的第 2 行做了兩次分解:
以上範例的 .NET Fiddle 連結:https://dotnetfiddle.net/qUqCqZ
小結
整理 deconstructor 的一些要點:
先用一個
Tuple
範例來看看「分解」實際上指的是什麼。註 1:Deconstructor 很容易被看成是 destructor。前者是 C# 7 新增的分解方法,後者是所謂的「解構子」(解構函式)。,
註 2:寫這篇筆記時,我沒有多方查證,而是把 deconstructor 擅自譯為「分解式」。歡迎提供更好的譯法。
沿用上一篇文章的範例,這次只是稍微修改先前的
ShowEmpInfo()
函式:public void ShowEmpInfo()
{
var emp = ("王大同", 50); // tuple literal
var (empName, empAge) = emp; // deconstruction declaration
Console.WriteLine($"{empName} {empAge}"); // "王大同 50"
}
請看倒數第 3 行,等號的右邊是一個
Tuple
物件。等號的左邊,則使用了所謂的分解式宣告(deconstruction declaration)的語法。這行程式碼的作用是:把一個 Tuple
物件裡面的元素依序指派給等號左邊的括弧中宣告的變數。然後,我們就可以直接使用這些變數(倒數第 2 行)。現在讓我們來看看,如何讓自己設計的類別提供「分解」能力。
先前提過,分解方法的名稱必須是 “Deconstruct”。此方法的回傳型別必須是
void
,而它所分解出來的東西,是透過輸出參數來提供給呼叫端。請看以下範例:public class Box
{
public int Width { get; } // 唯讀的自動屬性
public int Height { get; } // 唯讀的自動屬性
// 建構式(constructor)
public Box(int width, int height)
{
Width = width;
Height = height;
}
// 分解式(deconstructor)
public void Deconstruct(out int width, out int height)
{
width = this.Width;
height = this.Height;
}
// 請留意建構式和分解式兩者的相似與相異處;
// 在設計你的類別時,這兩種函式可能會成對出現。
}
如此一來,類別
Box
就具備了將本身包含的資訊(寬與高)拆解成兩個 int
變數的能力。底下是應用例:var box = new Box(20, 50);
var (width, height) = box; // 將 box 物件分解成兩個變數
Console.WriteLine($"寬={width}, 高={height}");
其中第 2 行完成了件事:宣告兩個
int
變數(width
和 height
),然後呼叫 Box
的 Deconstruct
方法來分別設定那兩個變數的初始值。上例的分解式不一定要寫成一行,你也可以將物件分解至事先宣告的變數,像這樣:
int width;
int height;
(width, height) = box; // 將 box 物件分解成兩個變數
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/VKwB83
分解式可以多載
一個類別可以有多個分解式,像這樣:
public void Deconstruct(out int width, out int height)
{
(略)
}
public void Deconstruct(
out int left, out int top, out int right, out int bottom)
{
(略)
}
既然與一般的方法宣告相同,那我們當然也可以直接呼叫它:
var box = new Box(20, 50);
box.Deconstruct(out int width, out int height);
// 上一行使用了本章稍早介紹過的 `out` 變數的宣告語法。
分解式可以寫成擴充方法
分解式也能寫成擴充方法,而不一定要寫在類別裡面。同樣延續先前的範例,假設我們無法取得
Box
類別的原始碼,我們依然能透過擴充方法來為它提供分解式:public static class BoxExtensions
{
public static void Deconstruct(this Box box, out int width, out int height)
{
width = box.Width;
height = box.Height;
}
}
如果類別本身已經提供了分解式,你又以擴充方法另外寫了相同 signature 的擴充方法 ,此時編譯器會選擇使用類別本身提供的分解式。
OK,現在我們看得很清楚了:`Deconstruct` 方法與一般的 C# 方法沒有太大差異,只是方法名稱得按規定,不能隨便取。這裡要再特別指出的是,實際上會呼叫哪一個分解式,是在編譯時期就決定的,這表示 dynamic 變數無法使用物件的分解式。因此,底下的寫法無法通過編譯:
dynamic box = new Box(10, 10);
box.Deconstruct(out int width, out int height); // 編譯失敗!
巢狀分解
C# 7 的分解式還支援巢狀分解。為了解釋這個稍稍複雜一點的語法,這裡仍沿用先前的例子,把
Box
類別改寫一下public class Box
{
public int Width { get; }
public int Height { get; }
public Box(int width, int height)
{
Width = width;
Height = height;
}
// 一號分解式
public void Deconstruct(out int width, out int height)
{
width = this.Width;
height = this.Height;
}
// 二號分解式
public void Deconstruct(out int width, out int height, out Box box)
{
width = this.Width;
height = this.Height;
box = new Box(width / 2, height / 2); // 內盒是外盒的一半大小
}
}
現在
Box
類別的分解式有兩個多載版本,而新加入的分解式(註解標示「二號分解式」)允許傳入三個參數,這第三個參數的型別也是 Box
(當然也可以是其他型別,這裡用現成的類別只是方便解釋)。那麼,在應用時,可以這樣寫:
var box = new Box(20, 50);
var (width, height, (innerWidth, innerHeight)) = box; // 巢狀分解
Console.WriteLine($"內盒寬高 = {innerWidth} x {innerHeight}");
其中的第 2 行做了兩次分解:
- 分解
var
宣告的最外層括弧,我們可以用簡短代號,把它看成(w, h, (x))
,可能會比較好理解。也就是說,第一層括弧裡面有三個變數,因此這裡會先呼叫的是帶有三個參數的那個,也就是二號分解式。 - 承上,
(w, h, (x))
中的x
還需要進一步拆解成兩個int
變數,所以接著要再呼叫帶有兩個參數的分解式,即一號分解式。
以上範例的 .NET Fiddle 連結:https://dotnetfiddle.net/qUqCqZ
小結
整理 deconstructor 的一些要點:
- deconstructor 不是 destructor;deconstructor 的函式名稱是 Deconstruct。
- deconstructor 不只能用來分解 Tuple;只要類別有提供合適的 Deconstruct 方法就可以分解。
- deconstructor 可以多載,也可以寫成擴充方法。由於是編譯時期進行方法解析,故 dynamic 變數無法使用 Deconstruct 方法(即無法通過編譯)。
- 可巢狀分解。範例:var (x, (y, z)) = anObject;
- 中文叫做「分解式」好嗎?