類型類是另外一項正被考慮引入.NET未來版本的特性。在提案“外觀和擴展(Shapes and Extensions)”中,該特性被稱為外觀,它們將大幅提升.NET泛型的能力。Mads Torgersen這樣描述類型類:
接口抽象的是作為類型實例的對象和值的“外觀(shape)”。從根本上講,類型類背后的思想是抽象類型本身的外觀。而且,當通過類型聲明引入需要的類型實現一個接口時,其他人可以在單獨的代碼中實現類型類。
類型類解決了一個長期存在的接口問題:它們無法處理靜態函數或操作符重載。這導致了一些問題,比如,在數學庫中,對于不同的數值數據類型,需要反復聲明相同的函數。
Mads總結道:
一般來說,外觀的聲明和接口聲明非常像,但它:
幾乎可以定義任意類型的成員(包括靜態成員)
可以通過擴展實現
可以在特定的地方像類型一樣使用
最后一個限制很重要:外觀不是類型。外觀的主要目的是作為泛型的一種約束,限定類型參數,保證它們有正確的外觀,并允許泛型聲明體使用那個外觀。
與外觀的思想緊密相關的是一種經過改進的擴展語法。擴展結構幾乎可以為類型類提供任何東西,而不只是方法擴展。考慮下面這個最簡單的例子:
public shape SNumberInt32類型已經提供了大部分內容,但它缺少zero屬性。擴展可以修復這個問題:
public extension IntGroup of int : SNumber然后,你可以像下面這樣使用它:
public static AddAll實現
這實現起來需要一些接口和結構方面的技巧。
Shapes被翻譯成了接口,每個成員(甚至是靜態成員)都轉換成了接口中的實例成員; 擴展被翻譯成了結構,每個成員(甚至是靜態成員)轉換成了結構中的實例成員; 如果擴展實現了一個或多個彎管,則底層的結構實現了那些外觀的底層接口。通常,上述結構被稱為“見證結構(witness struct)”。它的存在可以證明一個類遵循外觀的規則。或者換句話說,該類在類型類中。
編譯器會將上述AddAll方法翻譯成如下代碼:
public static T AddAll然后,上述見證結構就可以用于向AddAll方法提供必要的功能。結構可以直接在類型上調用方法或者根據需要使用擴展結構。
在類和接口中實現外觀
使用和我們擴展基類及實現接口一樣的語法,類可以顯式實現一個外觀。然后,編譯器會提供相應的見證結構。
也可以將接口標記為滿足外觀的要求。下面是一個例子:
public extension Comparable由于IComparable和理論上的類型類之間存在一對一關系,所以我們不需要為擴展結構提供擴展體。
泛型類型
事實證明,泛型類型有他們自己的問題。和泛型方法一樣,向泛型類添加外觀或者類型類作為類型約束需要額外提供一個類型參數。在泛型類上,由于類型參數的數量是其名稱的一部分,所以這會導致它和其它名稱相同的泛型類型發生沖突。
擴展外觀
擴展結構不僅可以用于實現外觀,還可以擴展它們。因此,你可以向現有的外觀中添加新方法、靜態方法及操作符。正如擴展方法一樣,語法是一樣的,就像它們在底層類型上直接定義了一樣。
評論
總的來說,人們對于該特性的反應不錯。不過,也有一些修改請求。例如,外觀目前必須顯式實現。有些開發人員希望,如果特定的類或接口不需要額外擴展方法時,就由編譯器隱式實現。Mads列舉了這樣做的一些問題:
那可能會導致,為了見證以相同的方式應用到同一類型的同一個外觀而生成許多結構類型,有生成的類型過度擴散的風險。如果編譯器比較聰明,每個程序集只生成一個,或許可以緩解這種情況,但我們從匿名類型了解到,這種重復數據刪除技術非常困難,而且很容易出錯。
如果我們允許泛型類型擁有外觀約束的類型參數,那么同一個東西擁有多個見證結構會導致實例化的泛型類型具有不同的類型標識,無法互換。
人們還擔心外觀和擴展綁定得太緊。他們認為,那將來可能會引起混淆。
對此,Mads答復說:
合并:在我的提案里,“擴展”實際上合并了多個問題:
[……]
我覺得,對于上述服務于所有這些目的的語言機制,有太多內容需要討論——但歸根結底,它們的關系非常密切。如果有一個提案可以將它們清晰地分開,那將是非常有意義的。那也許會更加簡單有效。
查看英文原文:.NET Futures: Type Classes and Extensions