Out 變數 (out variables)
在先前版本的 C# 中,out 參數的使用並不如我們期望的那麼的流暢。呼叫帶有 out 參數的 method 之前,你必須先宣告變數
並且將它當作 out 的參數傳遞才行。通常你不會 (也不需要) 先初始化這變數 (變數的內容會在被呼叫的 method 內覆寫),同時你也不能使用 var 的方式來宣告它, 你必須很明確的指定這變數的完整型別:
public void PrintCoordinates(Point p)
{
int x, y; // have to "predeclare"
p.GetCoordinates(out x, out y);
WriteLine($"({x}, {y})");
}
在 C# 7.0,新增了 out 變數,可以在傳遞 out 參數時同時宣告這個變數:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
請留意,這個變數在包含它本身的 { } 封閉區塊範圍內,所以接續宣告後面的程式碼可以直接使用這些變數。
多數類似型態的語法沒有指定可視範圍,該變數可視範圍就等同於宣告他的區塊範圍。
通常 out 變數都會直接被宣告為傳遞的參數,編譯器通常能直接判定參數的型別為何 (除非 method 包含數個互相衝突
的 overloads 而無法判定),因此可以直接使用 var 的方式來宣告它:
p.GetCoordinates(out var x, out var y);
一般來說,我們常常在 Try... 這類的使用模式中用到 out 參數,它會傳回 true 或是 false 來代表執行成功與否,同時藉著 out 參數來傳回成功執行後的結果:
public void PrintStars(string s)
{
if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
else { WriteLine("Cloudy - no stars tonight!"); }
}
如果你不在意某個 out 參數的傳回結果,可以使用 _ 代表忽略它:
p.GetCoordinates(out var x, out _); // I only care about x
Constant Patterns (常數模式, 以 c 表示,c 是 C# 的常數表達式), 測試輸入的數值是否與 c 相等。
Type Patterns (類型模式, 以 T x 表示,T 代表型別,而 x 是識別名稱), 測試輸入的數值是否屬於類別 T? 如果是的話就把輸入的數值放到類型為 T 的變數 x 中。
Var Patterns (變數模式, 以 var x 表示, x 是識別名稱), 這種模式下永遠會匹配成功,此時 x 的型別與輸入的數值相同,這模式下只是簡單的把輸入的數值放到 x 之中。
這些只是計畫中的第一步 - pattern (模式) 是 C# 新型態的語法元素,我們期望未來能繼續新增更多的功能。
在 C# 7.0 我們用 pattern 來增強兩種既有的語法結構:
is expression (is 表達式) 現在可以在右方加上 pattern,在之前則只能定義型別。
switch 陳述式中的 case 子句,現在可以比對模式是否符合,過去則只支援常數數值。
在未來的 C# 我們會增加更多適用 pattern 的語法。
使用 pattern 的 is 表達式
來看看使用 constant patterns 與 type patterns 的 is expression 使用範例:
public void PrintStars(object o)
{
if (o is null) return; // constant pattern "null"
if (!(o is int i)) return; // type pattern "int i"
WriteLine(new string('*', i));
}
如所見,pattern 變數 - 由 pattern 引入的變數,跟前面介紹的 out 變數非常相似,你可以宣告在表達式之中,而且可以直接就近在同可是範圍內直接使用他。
跟 out 變數很相似的地方是,模式變數是可變動的,我們常將 out 變數與 pattern 變數,統稱為 expression 變數。
Patterns 常與 Try... method 一起使用:
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }
switch 陳述式現在可以運用在所有型別 (不再只限於基本類型)
patterns 可以用在 case 子句
case 子句可以附加條件判斷式
這邊有對應的範例程式碼:
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
這裡有幾個 switch 陳述式新增的擴充功能:
case 子句的順序是重要的:
就如同 catch 子句一樣,多個 case 子句之間不再是沒有順序關聯的,而第一個符合條件的 case 子句會被選中。這點非常重要,拿上一個範例程式碼來說,代表正方形的這個 case 子句 (譯註: case Rectangle s when (s.Length == s.Height):) 應該要排在代表長方形的 case 子句 (case Rectangle r:) 前面,結果才會正確。另外,就像 catch 子句一樣,編譯器可以標示出永遠無法執行到的程式碼來協助你。在這之前,你無法也不需要指定多個 case 之間的評估順序,所以這並不是個破壞性的改變 (breaking change)。
default 子句永遠會最後才評估:
即使在上述的例子中,null case 子句被擺在最後,他仍然會在 default 子句之前被檢查。這樣的設計是為了與現有的 switch 陳述句保持相容。然而,好的做法通常會明確的將 default 子句擺在最後面。
擺在最後面的 null case 子句並不會無法被被執行到:
因為 type patterns (類型模式) 依循 is expression 的例子,不會與 null 子句匹配。這可以確保 null 子句不會不小心被任何的 type patterns (類型模式) 給搶走,你必須更清楚該如何處理這種狀況 (或是把它留給 default 子句來處理)。
由 case ... 引進的 pattern 變數 ,他的可視範圍只限於對應的 switch 區段。