<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 原則29:讓接口支持協變和逆變 **By D.S.Qiu** **尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)** 類型可變性,具體地,協變和逆變,定義了一個類型變化為另一個類型的兩種情況。如果可能,你應該讓泛型接口和委托支持泛型的協變和逆變。這樣做可以讓你的 APIs 能安全地不同方式使用。如果你不能將一個類型替換為另一個,那么就是不可變。 類型可變性是很多開發者遇到的卻又不真正理解的很多問題之一。協變和逆變是類型替換的兩種不同形式。如果你用聲明類型的子類返回那么就是協變的。如果你用聲明類型的基類作為參數傳入那么就是逆變。面向對象原因普遍支持參數類型的協變。你可以傳遞子類對象到任何期望是基類參數的方法。例如, Console.WriteLine() 函數有一個使用 System.Object 參數的版本。你可以傳入任何 System.Object 的子類對象。如果你重載實例方法返回 System.Object ,你可以返回任何繼承自 System.Object 的對象。 普遍的行為讓很多開發者認為泛型也遵循這個規則。你可以使用 IEnumerable&lt;MyDerived&gt; 傳給參數為 IEnumerable&lt;Object&gt; 的方法。你會期望返回的 IEnumerable&lt;MyDerivedType&gt; 可以賦值給 IEnumerable&lt;Object&gt; 變量。不是這樣的。在 C# 4.0之前,所有泛型類型都是不可變的。這意味著,很多次你都自以為泛型也有協變和逆變時,編譯器卻告訴你的代碼是有問題的。數組是被看做協變的。然而,數組不支持安全的協變。隨著 C# 4.0 ,新關鍵字可以讓你的泛型支持協變和逆變。這使得泛型更有用,特別是在泛型接口和委托上你應該盡可能使用 in 和 out 參數。 我們開始通過數組理解協變的問題。考慮下面簡單的類繼承結構: ``` abstract public class CelestialBody { public double Mass { get; set; } public string Name { get; set; } // elided } public class Planet : CelestialBody { // elided } public class Moon : CelestialBody { // elided } public class Asteroid : CelestialBody { // elided } ``` 下面這個方法把 CelestialBody 對象數組當做協變,而且那樣做事安全的: ``` public static void CoVariantArray(CelestialBody[] baseItems) { foreach (var thing in baseItems) Console.WriteLine("{0} has a mass of {1} Kg", thing.Name, thing.Mass); } ``` 下面這個方法也把 CelestialBody 對象數組當做協變,但這是不安排的。賦值語句會拋出異常。 ``` public static void UnsafeVariantArray( CelestialBody[] baseItems) { baseItems[0] = new Asteroid { Name = "Hygiea", Mass = 8.85e19 }; } ``` 如果你將子類賦給基類的數組元素一樣會有相同的問題: ``` CelestialBody[] spaceJunk = new Asteroid[5]; spaceJunk[0] = new Planet(); ``` 把集合看著協變意味著當如果有兩個類有繼承關系是,你可以認為他們的關系和兩個類型的數組是一樣的。這不是一個嚴格的定義,但要記住它是很用的。 Planet 對可以傳遞給任何期望參數為 CelestialBody 的方法。這是因為 Planet 繼承于 CelestialBody 。類似地,你可以將 Planet[] 傳遞給任何期望參數為 CelestianlBody[] 的方法。但是,正如上面的例子一樣,它們總是不能如你期望一樣工作。 當泛型被引入時,這個問題被十分嚴格的處理。泛型總是被當做不可變的。泛型類型不得不正確匹配。然而,在 C# 4.0,你可以將方向接口修飾變為協變或逆變。我們先討論泛型協變,而后在討論逆變。 下面這個方法調用參數為 List&lt;Planet&gt; : ``` public static void CoVariantGeneric( IEnumerable<CelestialBody> baseItems) { foreach (var thing in baseItems) Console.WriteLine("{0} has a mass of {1} Kg", thing.Name, thing.Mass); } ``` 這是因為 IEnumerable&lt;T&gt; 已經被擴展為限制 T 只能出現在輸出位置: ``` public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> :IDisposable, IEnumerator { T Current { get; } // MoveNext(), Reset() inherited from IEnumerator } ``` 我給出了 IEnumerable&lt;T&gt; 和 IEnumerator&lt;T&gt; 的定義,因為 IEnumerator&lt;T&gt; 會有比較重要的限制。注意到 IEnumerator&lt;T&gt; 現在的參數類型 T 已經被修飾符 out 修飾。這就強制編譯器類型 T 只能在輸出位置。輸出位置僅限于函數返回值,屬性 get 訪問器和委托的參數。 因此,使用 IEnumerable&lt;out T&gt; ,編譯器知道你會查看序列的每個 T ,但是不會修改序列的內容。這個例子中把 Planet 當做 CelestailBody 就是這樣的。 IEnumerable&lt;T&gt; 可以協變是因為 IEnumerator&lt;T&gt; 也是協變的。如果 IEnumerable&lt;T&gt; 返回的接口不是協變的,編譯器會產生一個錯誤。協變類型必須返回值類型的參數或這個接口是協變的。 然而,下面方法替換隊列的第一個元素的泛型是不可變的: ``` public static void InvariantGeneric( IList<CelestialBody> baseItems) { baseItems[0] = new Asteroid { Name = "Hygiea", Mass = 8.85e19 }; } ``` 因為 IList&lt;T&gt; 的參數 T 既沒有被 in 又沒有被 out 修飾符,你必須使用正確的類型進行匹配。 當然,你也可以創建逆變泛型接口和委托。用 in 修飾符替換 out 。這個告訴編譯器類型參數只能出現在輸入位置。.NET 框架已經為 IComparable&lt;T&gt; 加上了 in 修飾符: ``` public interface IComparable<in T> { int CompareTo(T other); } ``` 這說明如果 CelestialBody 實現 IComparable&lt;T&gt; ,可以使用很多不同的對象。它可以比較兩個 Planet ,一個 Planet 和一個 Moon ,一個 Moon 和一個 Asteroid ,或者其他組合。比較了多個不同的對象,但這是有效的比較。 你會注意到 IEquatable&lt;T&gt; 是不可變的。按照定義, Planet 對象不會和 Moon 對象相等。它們是不同的類型,所以沒有意義。如果兩個對象是相同類型的如果相等而且不充分的,它是必要的(查看原則6)。 類型參數是可逆變的只有作為方法參數或某些地方的委托參數。 現在,你應該已經注意到我已經用了詞組“某些地方的委托參數”兩次。委托的定義可以協變也可以逆變。這是相當簡單:方法參數逆變( in ),方法的返回值是協變( out )。BCL 更新了包括下面變種的很多委托的定義: ``` public delegate TResult Func<out TResult>(); public delegate TResult Func<in T, out TResult>(T arg); public delegate TResult Func<in T1, T2, out TResult>(T1 arg1,T2 arg2); public delegate void Action<in T>(T arg); public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); public delegate void Action<in T1, in T2, T3>(T1 arg1, T2 arg2, T3 arg3); ``` 在重復一次,這也許不太難。但是,如果你把它們混淆了,事情就得開動你的腦筋了。你已經看到你不能從協變接口返回不可變接口。你使用委托要么限制協變要么限制逆變。 如果你不仔細的話,委托在接口里會向協變和逆變偏移。這里有幾個例子: ``` public interface ICovariantDelegates<out T> { T GetAnItem(); Func<T> GetAnItemLater(); void GiveAnItemLater(Action<T> whatToDo); } public interface IContravariantDelegate<in T> { void ActOnAnItem(T item); void GetAnItemLater(Func<T> item); Action<T> ActOnAnItemLater(); } ``` 接口里的方法的命名展示了它們具體的工作。仔細看 ICovariantDelegate 接口的定義。 GetAnItemLater() 只是檢索元素。方法中可以調用 Func&lt;T&gt; 返回檢索的元素。 T 仍然出現在輸出位置上。這可能是有意義。 GetAnItemLater() 很容易讓人困擾。這里,你的委托方法只是接收 T 對象。所以,即使 Action&lt;T&gt; 是協議的,它出現的 ICovarinatDelegate 接口的位置其實是 T 由實現 ICovariantDelegate&lt;T&gt; 的對象返回的。它看起來是逆變的,但是相對于接口來說是協變的。 IContravariantDelegate&lt;T&gt; 和一般的接口一樣但是展示如何使用逆變接口。再說一次, ActOnAnItemLater() 方法就很明顯。 ActOnAnItemLater() 方法有些復雜。你返回一個接受 T 類型對象的方法。這個最后方法,一次又一次強調,會引起一些困擾。它和其他接口的概念是一樣的。 GetAnItemLater() 方法接受一個方法并返回 T 對象。即使 Func&lt;out T&gt; 聲明為協變,它的作用是為實現 IContravariantDelegate 對象引入輸入。它相對于 IContravariantDelegate 的作用是逆變的。 描述協變和逆變如何正確的工作十分復雜。值得慶幸的是,語法現在支持使用 in (逆變) 和 out (協變)修飾接口。你應該盡可能使用 in 或 out 修飾符修復接口和委托。然后,編譯器就會糾正和你定義的有差異的用法。編譯器會捕獲到接口和委托的定義,并且發現你創建的類型的任何誤用。 小結: 這個原則作為第三章的最后一個,雖然介紹的是類型的可變性,有些類似類型轉換,但是情況卻復雜的多,理解起來難度很大,想要更徹底的理解協變和逆變的概念,可以參考①。 歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德! 有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創! 如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。 轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2086977](/blog/2086977) 更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風) 參考: ①[1-2-3.cnblogs.com](http://www.cnblogs.com/1-2-3/): [http://www.cnblogs.com/1-2-3/archive/2010/09/27/covariance-contravariance-csharp4.html](http://www.cnblogs.com/1-2-3/archive/2010/09/27/covariance-contravariance-csharp4.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看