where (generic type constraint, 泛型類型條件約束)

2014年7月

小菜鳥的程式碼裡面一堆 T、一堆 where  ( generic type constraint,泛型類型條件約束),於是就被問說:「你知道 where 是什麼嗎?」

小菜鳥答:「定義,感覺就像定義。」

 

Sql 裡面的 where、Linq 中使用的 where 都是條件篩選,怎麼可能在泛型類型這變成了定義的用途呢?

後來小菜鳥簡單寫了一 interface、一 class,如下:

01_準備

接著小菜鳥寫了一個簡單的method,準備來看看where的用途。

02_Method

在沒有 where 條件下,無論是 int、string、List<int>、Address  都可以呼叫 PrintType<T>(),但一加了 where 條件就未必了。

加了 where T : struct 之後,就可以收到 Visual Studio 送來的編譯錯誤通知:「類型 ‘ConsoleApp.Address’ 必須是不可為 null 的實值類型,才能在泛型類型或方法 ‘ConsoleApp.Program.PrintType<T>()’ 中做為參數 ‘T’ 使用」。

03_whereStruct

其實,where 可解釋為限制外部呼叫的條件,所以微軟官網才會將其翻譯解釋為 generic type constraint,泛型類型條件約束。故當 PrintType<T>()  加上 where T : struct  之後,就等於限制了 T 必須是實值型別。

 

作者:「所以說……小菜鳥,別人解釋where時,是解釋為『限制』,沒有錯啊。」

小菜鳥:「我沒有說他們錯啊……只是我把它解釋為定義而已嘛……我只是感覺良好而已嘛……」

不知何時,小菜鳥已經蹲到角落畫圈圈了。

在小菜鳥畫完圈圈之前,來看看微軟官方網站上,比較正式、正確的解說:「定義泛型類別時,可限制用戶端程式碼在執行個體化類別時,型別引數可以使用哪些型別。 如果用戶端程式碼嘗試使用條件約束所不允許的型別來執行個體化類別,就會產生編譯時期錯誤。 這些限制稱為條件約束。」

接著再來說明一下 where 可以用來限制的類型,共分為 6 種。

Where T : 條件內容
struct 限制 T 須為非 Nullable 的實值型別。包含:bool、byte、char、decimal、double、enum、float、int、long、sbyte、short、struct、uint、ulong、ushort 等
class 限制T須為參考型別。C# 內建的參考型別有 dynamic、object、string;其他自訂宣告的 class、interface、delegate,以及複合型的型別如 List<int>等皆屬於參考型別
new() 限制 T 須擁有公開的無參數建構式。當此條件與其它條件同時進行限制,須將此限制指定為最後。簡而言之,where T : new() 表示可進行「 T newitem = new T(); 」
<class name> 限制 T 須為該 class 型別本身、或衍生自該 class 的型別。又或者可換個方向思考:限制T可轉換為該 class 型別
<interface name> 限制 T 須為該 interface 型別本身、或衍生自該 interface 的型別。又或者可換個方向思考:限制T可轉換為該 interface 型別
U 限制 T 須為 U、或衍生自 U 的型別。使用方式:where T : U※ 此一限制如同使用 class name、或 interface name 進行限制

 

所以不妨來看看幾個 Example 吧。

04_struct

05_class

06_new

07_className

08_InterfaceName

09_int

10_NullableInt

 

因為一使用了 where 泛型類型條件約束後,Visual Studio 在編譯時就會進行檢查是否符合要求,若有存在不合要求的話,就會丟出錯誤,導致程式無法執行。

 

作者:「小菜鳥,問一下喔~ 可不可以同時限制這個 T 為 struct、跟 class?」

小菜鳥想了一下,「必須是實值型別、也必須是參考型別……?!可是 C# 裡面資料也只分這兩種型別啊,就這麼二分法還要兩者皆是?!唔……」

作者:「微軟官網上也沒說不可以嘛!」

小菜鳥看了看官網,然後……就來試試看了。

14_structAndClass

Visual Studio 說:「’class’ 或 ‘struct’ 條件約束必須在任何其他條件約束的前面。」

由此可知,同時限制多個條件時,class 必須排第一個、struct 也必須排第一個,但第一個又只能有一個,那就……只能二選一啦! struct、跟 class 就無法同時限制了。

 

作者:「小菜鳥,那可不可以同時限制這個 T 為 struct、跟 new()?」

小菜鳥:「可是實值型別裡面,看起來沒有一個可以進行 new 出物件的啊。」

作者:「微軟官網上也沒說不可以嘛……難道你就不好奇?」

小菜鳥:「呃,其實我是挺好奇的……」

結果──

Visual Studio 說:「’new()’ 條件約束不能和 ‘struct’ 條件約束一起使用。」

好吧,就算微軟官網上沒講、或者是其他頁面才有說明,但 Visual Studio 倒是講得清清楚楚。

 

到目前為止,都是考慮呼叫該 PrintType<T>() 方法時會受到 where 條件限制的地方,現在不妨換個角度來看看 where 吧!

讓 PrintType<T>() 受到 IAddress 限制,並傳遞一個 T 型別的參數 item 進去。

11_useT

由於這時候傳遞進去的參數 item 之型別為T、而T已經被限制為 IAddress 的結構,所以在 PrintType<T>() 方法中,IAddress 有哪些欄位、屬性、方法,item 就可以直接使用。

12_useIAddress

13_useIAddress

 

小菜鳥:「所以你看嘛!這不就是定義、宣告嗎!開頭就先定義清楚 T 是 IAddress 的一種,所以我裡面就可以把 item 視為一個 IAddress 去使用它的成員啦!」

作者:「好啦、好啦,也沒有人說你錯不是嗎?你要自我感覺良好也沒有人說不可以啊~」

小菜鳥拿起筆,準備繼續畫圈圈。

作者想了想,又說:「小菜鳥,其實你是學生時代數學讀太多、或證明題寫太多的關係吧?你的說法就像是『因為 A 是有理數,所以可以將 A 表示為分數』的感覺。」

小菜鳥:「……哼,不理你了。」

看著小菜鳥離開的背影,作者忍不住疑惑:「奇怪,我剛剛有說錯什麼話嗎?」

《完》

 

相關參考網址:

1. where (泛型類型條件約束):http://msdn.microsoft.com/zh-tw/library/bb384067.aspx

2. 類型參數的條件約束:http://msdn.microsoft.com/zh-tw/library/d5x73970.aspx

3. 實值型別:http://msdn.microsoft.com/zh-tw/library/s1ax56ch.aspx

4. 參考型別:http://msdn.microsoft.com/zh-tw/library/490f96s2.aspx

5. 實值和參考型別:http://msdn.microsoft.com/zh-tw/library/4d43ts61(v=vs.90).aspx

 

One Response to where (generic type constraint, 泛型類型條件約束)

  1. 引用通告: 小菜鳥蹦達日記目錄 | Eli

發表留言