1. 程式人生 > >13.3.4 介面和委託的泛型可變性的複雜情況

13.3.4 介面和委託的泛型可變性的複雜情況

1.  Converter<TInput, TOutput> :同時使用協變性和逆變性

            Converter<object, string> converter1 = x => x.ToString();
            Converter<string, string> converter2 = converter1;
            Converter<object, object> converter3 = converter1;
            Converter
<string, object> converter4 = converter1;

程式碼清單13-15展示了委託型別 Converter<object, string> (一個接收物件,生成字串的委託)的可變性轉換。我們首先使用簡單的Lambda表示式(呼叫 ToString )實現了委託。
我們恰巧從未真正呼叫該委託,因此完全可以使用空引用。但我發現如果可以定義一個在呼叫時發生的具體行為,將有助於理解可變性。接下來的兩行程式碼相對簡單,每次只需關注一種型別引數即可。 TInput 型別引數只用於輸入,因此可以逆變地將 Converter<object,string> 當作 Converter<Button,string>來使用。換句話說,既然可以將任意物件的引用傳遞給轉換器,那麼自然可以傳遞一個Button引用。同樣,TOutput型別引數只用於輸出(返回型別),因此可以協變地使用它:如果轉換器總是返回一個字串引用,那麼在你能夠保證返回一個物件引用的地方,可以放心地使用該轉換器。最後一行僅僅是對這種理念的一種合理延伸。它在同一個轉換內同時使用了逆變性和協變性,得到的是一個只接受按鈕並且返回物件引用的轉換器。注意,如果不進行強制轉換,則無法將其轉換為原來的型別——我們已經基本上在每個點上都放鬆了要求,你不能再隱式地收緊了。

2. 瘋狂的高階函式

        delegate Func<T> FuncFunc<out T>();
        delegate void ActionAction<out T>(Action<T> action);
        delegate void ActionFunc<in T>(Func<T> func);
        delegate Action<T> FuncAction<in T>();

每一個宣告都相當於將一個標準的委託嵌入到另一個之中。
例如, FuncAction<T> 等同於Func<Action<T>> ,它們都表示一個函式,返回以 T 為引數的 Action 。但它應該是協變的還是逆變的呢?
這個函式將返回與 T 相關的內容,似乎應該是協變的,但是它也傳入了與 T 有關的內容,似乎又是逆變的。
答案是該委託對 T 是逆變的,因此宣告時使用了 in 修飾符。
作為一個便捷的規則,可以認為內嵌的逆變性反轉了之前的可變性,而協變性不會如此。
因此 Action<Action<T>> 對 T 來說是協變的, Action<Action<Action<T>>> 是逆變的。
相比之下,對於 Func<T> 的可變性來說,你可以編寫 Func<Func<Func<... Func<T>...>>> ,巢狀任意多個級別,得到的仍然是協變性。
舉一個使用介面的類似示例,假設可以使用比較器對序列進行比較。如果能夠比較任意物件的兩個序列,自然可以比較兩個字串序列,但反之則不然。
將其轉換為程式碼(不必實現介面)如下:

            IComparer<IEnumerable<object>> objectComparer = null;
            IComparer<IEnumerable<string>> stringComparer = objectComparer;

這種轉換是合法的:由於 IEnumerable<T> 的協變性,IEnumerable<string> 是比IEnumerable<object> 更“小”的型別;
而 IComparer<T> 的逆變性可以將“較大”型別的比較器轉換為較小型別的比較器。
當然,我們在本節只使用了包含單個型別引數的委託和介面——它也完全可以應用於多個型別引數。
儘管這種型別的可變性讓你痛不欲生,不過不必擔心,你並不會頻繁地使用它,而且用的時候編譯器也會幫你大忙。
我只是想讓你知道有這種可能。另一方面,你可能認為某些功能可以實現,但實際上它們並沒有得到支援。