1. is 運算子:檢查型別相容性
在實際應用裡,常常你手上只有一個基底類別的參考,但你知道或懷疑記憶體裡的實體其實是更特定的衍生型別。這時你可能想要存取那個衍生型別獨有的成員。為了安全又方便地處理這種情境,C# 給你幾個關鍵工具:is、as,還有現代語言版本裡超強的 pattern matching(樣式比對)。
is 可以檢查一個物件是不是指定型別、它的衍生型別,或有沒有實作某個 interface。如果相容就回傳 true,不相容就 false。
主要用途:判斷一個物件能不能安全轉型成某個型別。
範例:檢查基底型別和繼承
class 動物 { public string 物種 { get; set; } = "未知"; }
class 狗 : 動物 { public void 吠叫() => Console.WriteLine("Woof!"); }
class 貓 : 動物 { public void 喵() => Console.WriteLine("Meow!"); }
動物 我的動物 = new 狗 { 物種 = "黃金獵犬" };
Console.WriteLine($"我的動物 is 動物: {我的動物 is 動物}"); // True
Console.WriteLine($"我的動物 is 狗: {我的動物 is 狗}"); // True
Console.WriteLine($"我的動物 is 貓: {我的動物 is 貓}"); // False
在這個例子裡,我的動物 實際上是 狗。所以 我的動物 is 動物 跟 我的動物 is 狗 都會回傳 true。
範例:檢查 null
is 運算子遇到 null 也會有預期的行為。
動物 空動物 = null;
狗 特定狗 = new 狗();
Console.WriteLine($"空動物 is 動物: {空動物 is 動物}"); // False (null 不是任何型別的實體)
Console.WriteLine($"特定狗 is null: {特定狗 is null}"); // False (物件不是 null)
注意,null 不是任何型別的實體,所以 null is MyType 永遠 false。但 someObject is null 是個方便又安全的方式來檢查參考是不是 null。
範例:is 搭配 interface
is 也可以用來檢查有沒有實作 interface。
interface 可飛 { void 飛(); }
class 鳥 : 動物, 可飛 { public void 飛() => Console.WriteLine("Flap flap!"); }
class 魚 : 動物 { }
動物 生物 = new 鳥();
Console.WriteLine($"生物 is 鳥: {生物 is 鳥}"); // True
Console.WriteLine($"生物 is 可飛: {生物 is 可飛}"); // True
生物 = new 魚();
Console.WriteLine($"生物 is 可飛: {生物 is 可飛}"); // False
2. as 運算子:安全型別轉換
as 運算子是用來安全地把物件轉成指定型別。跟直接轉型 (Type)obj 不一樣,失敗時會丟 InvalidCastException,as 只會回傳 null,不會噴例外。這很適合你不確定物件實際型別的時候。
主要用途:嘗試轉型,如果失敗就拿到 null。
範例:as 的基本用法
class 形狀 { }
class 圓形 : 形狀 { public double 半徑 { get; set; } }
class 正方形 : 形狀 { public double 邊長 { get; set; } }
形狀 我的形狀 = new 圓形 { 半徑 = 5.0 };
// 嘗試轉型成圓形
圓形 圓 = 我的形狀 as 圓形;
if (圓 != null) // 一定要檢查 null!
{
Console.WriteLine($"這是個半徑為: {圓.半徑} 的圓"); // 輸出:這是個半徑為: 5 的圓
}
// 嘗試轉型成正方形
正方形 方 = 我的形狀 as 正方形;
if (方 == null) // 轉型失敗,方 == null
{
Console.WriteLine("這不是正方形。"); // 輸出:這不是正方形。
}
可以看到,as 讓你不用擔心執行時錯誤,失敗就回傳 null。
範例:as 的限制
要記得,as 只能用在參考型別和 nullable 值型別。不能用在一般值型別,因為它們不能是 null。
object 某個值 = 100;
int 整數 = 某個值 as int; // 編譯錯誤:'as' 不能用在非 null 型別
int? 可空整數 = 某個值 as int?; // OK,可空 int
Console.WriteLine($"可空 int: {可空整數}"); // 輸出:可空 int: 100
string 字串 = 某個值 as string; // 會回傳 null,因為 100 不是字串
Console.WriteLine($"從 int 轉來的字串: {字串 ?? "null"}"); // 輸出:從 int 轉來的字串: null
對於非 nullable 值型別,要嘛用直接轉型((int)某個值),前提是你很確定型別(也願意冒例外風險),要嘛用 pattern matching,這樣更推薦。
3. Pattern Matching(樣式比對)
Pattern matching 是個很強大又越來越進化的功能,讓你可以更優雅又安全地檢查型別、從物件裡取資料。這會大幅減少重複程式碼,也讓程式更好讀。
Type Pattern(型別樣式)搭配 is
這是最常用的 pattern matching 形式,可以檢查物件型別,如果成功,直接把它指定給新變數。
語法: 表達式 is 型別 變數
範例:取代 is + 轉型
// 繼續用 形狀、圓形、正方形 class
形狀 當前形狀 = new 圓形 { 半徑 = 7.5 };
// 舊的、囉唆的寫法:
if (當前形狀 is 圓形)
{
圓形 c = (圓形)當前形狀;
Console.WriteLine($"舊寫法:圓形半徑 {c.半徑}");
}
// 新的、優雅的 type pattern 寫法
if (當前形狀 is 圓形 c) // 檢查型別並建立 'c' 變數
{
Console.WriteLine($"新寫法:圓形半徑 {c.半徑}"); // 'c' 已經是圓形型別
}
形狀 另一形狀 = new 正方形 { 邊長 = 10.0 };
if (另一形狀 is 正方形 s)
{
Console.WriteLine($"這是邊長為 {s.邊長} 的正方形");
}
c(或 s)這個變數只在 if 區塊裡有效,這樣你不會不小心在錯誤型別時用到它。
Property Pattern(屬性樣式)
從 C# 8.0 開始,你不只可以檢查型別,還能同時檢查一個或多個屬性值,甚至把它們取出來給新變數。
語法: 表達式 is 型別 { 屬性1: 值樣式, 屬性2: 取出變數 }
範例:檢查屬性
形狀 測試形狀 = new 圓形 { 顏色 = "綠色", 半徑 = 12.0 };
// 檢查是不是圓形而且顏色是綠色
if (測試形狀 is 圓形 { 顏色: "綠色" })
{
Console.WriteLine("找到綠色圓形。");
}
// 檢查是不是圓形,並同時取出半徑
if (測試形狀 is 圓形 { 半徑: var r })
{
Console.WriteLine($"半徑取出來了: {r}");
}
// 結合檢查和取值:
if (測試形狀 is 圓形 { 顏色: "綠色", 半徑: var 半徑值 } 圓物件)
{
Console.WriteLine($"取到綠色圓形。半徑: {半徑值}, 物件: {圓物件.半徑}");
}
屬性樣式讓複雜的條件判斷變超簡單。
範例:Property Pattern 裡的範圍跟邏輯運算子
你現在可以檢查物件屬性有沒有符合某個條件:
圓形 大圓 = new 圓形 { 半徑 = 25.0, 顏色 = "藍色" };
// 檢查半徑大於 20 且顏色是藍色
if (大圓 is 圓形 { 半徑: > 20, 顏色: "藍色" })
{
Console.WriteLine("發現大藍圓。");
}
// 檢查半徑在 (5..15) 範圍
if (測試形狀 is 圓形 { 半徑: >= 5 and <= 15 })
{
Console.WriteLine("中型圓形。");
}
注意! 跟 bool 型別不一樣,這裡要用關鍵字:and、or 跟 not。
4. Switch Expressions 跟 Switch Statements 搭配 Pattern Matching
pattern matching 在 switch 裡最靈活也最好讀。你可以根據比對到的樣式做不同邏輯。
範例:Switch Statement
可以針對每個比對到的樣式寫一段程式。
// 動物、狗、貓 class 跟前面一樣
動物 當前生物 = new 狗 { 物種 = "貴賓狗" };
switch (當前生物)
{
case 狗 d: // 型別樣式:如果是狗,給 'd' 變數
d.吠叫();
Console.WriteLine($"這是 {d.物種} 品種的狗。");
break;
case 貓 c when c.物種 == "暹羅": // 型別樣式加條件 (when)
c.喵();
Console.WriteLine($"這是暹羅貓。");
break;
case 動物 a: // 如果只是動物(不是狗/貓)
Console.WriteLine($"這只是 {a.物種} 這種動物。");
break;
case null: // null 樣式,處理 null 值
Console.WriteLine("物件是 null。");
break;
default: // 其他都沒比對到
Console.WriteLine("未知生物。");
break;
}
switch 裡 case 的順序很重要:越特定的樣式要放前面。
範例:Switch Expression
這是 switch 的更精簡語法,會回傳一個值。很適合根據型別或屬性把物件轉成別的值。
語法: 表達式 switch { 樣式1 => 結果1, 樣式2 => 結果2, ... _ => 預設結果 }
形狀 處理形狀 = new 長方形 { 寬度 = 5, 高度 = 5, 顏色 = "紅色" };
string 形狀資訊 = 處理形狀 switch
{
圓形 { 半徑: var r } when r > 10 => $"大圓形 (R={r})", // 屬性樣式加條件
圓形 { 顏色: "藍色" } c => $"藍色圓形 (R={c.半徑})", // 屬性樣式加取值
圓形 c => $"普通圓形 (R={c.半徑})", // 單純型別樣式
長方形 { 寬度: var w, 高度: var h } when w == h => $"正方形 ({w}x{h})", // 正方形
長方形 r => $"長方形 ({r.寬度}x{r.高度})", // 其他長方形
null => "形狀不存在 (null)", // null 樣式
_ => "未知形狀" // _ 取代 'default'
};
Console.WriteLine(形狀資訊); // 輸出:正方形 (5x5)
switch expression 超適合短小精幹的轉換。
Var Pattern(var 樣式)
從 C# 7.0 開始,你可以在 pattern matching 裡用 var。var 會比對任何物件(除了 null),然後把它取出來給對應型別的變數。
範例:用 var Pattern
object obj = "Hello World";
if (obj is var 結果) // 永遠 true,結果會是 string
{
Console.WriteLine($"型別: {結果.GetType().Name}, 值: {結果}");
}
obj = 123;
string 型別名稱 = obj switch
{
var x when x is int => "這是整數",
var y when y is string => "這是字串",
_ => "其他東西"
};
Console.WriteLine(型別名稱); // 輸出:這是整數
var Pattern 很少單獨用來檢查型別,但在其他樣式或 switch 裡取值超方便。
5. is vs as vs Pattern Matching:什麼時候用哪個?
選哪個工具要看你要做什麼、還有你用的 C# 版本。
is 運算子(簡單):
用法:你只想檢查物件是不是某型別,而且後面不需要馬上轉型或用到特定成員。
if (我的物件 is 某型別)
as 運算子:
用法:你想要轉型參考型別(或 nullable 值型別),而且不想失敗時噴例外,會用 null 來判斷。
我的衍生類別 derived = 我的基底物件 as 我的衍生類別;
if (derived != null) { /* ... */ }
Pattern Matching(is 型別 變數):
現代推薦寫法:型別檢查跟安全轉型一次搞定,程式更好讀。所有型別都能用。
用法:你想要檢查型別並馬上用到特定成員。
if (我的物件 is 我的衍生類別 derived) { derived.特定方法(); }
Pattern Matching(switch expressions / statements):
現代推薦寫法:你想要根據物件型別、屬性或其他特徵做不同事或拿到不同值。這比一堆 if-else if 好太多。
用法:實作多型行為或根據物件特徵做複雜選擇邏輯。
string 取得形狀資訊(形狀 s) => s switch
{
圓形 c => $"圓形 R={c.半徑}",
長方形 r => $"長方形 W={r.寬度} H={r.高度}",
_ => "未知"
};
GO TO FULL VERSION