CodeGym /課程 /C# SELF /型別操作: is

型別操作: isas 和 Pattern Matching

C# SELF
等級 34 , 課堂 4
開放

1. is 運算子:檢查型別相容性

在實際應用裡,常常你手上只有一個基底類別的參考,但你知道或懷疑記憶體裡的實體其實是更特定的衍生型別。這時你可能想要存取那個衍生型別獨有的成員。為了安全又方便地處理這種情境,C# 給你幾個關鍵工具:isas,還有現代語言版本裡超強的 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 不一樣,失敗時會丟 InvalidCastExceptionas 只會回傳 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 型別不一樣,這裡要用關鍵字:andornot

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 裡用 varvar 會比對任何物件(除了 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.高度}",
    _ => "未知"
};
1
問卷/小測驗
延遲執行,等級 34,課堂 4
未開放
延遲執行
優化集合操作
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION