簡體   English   中英

C#協方差混淆

[英]C# covariance confusion

以下是關於C#協方差的代碼片段。 我對如何應用協方差有一些了解,但有一些我很難掌握的詳細技術內容。

using System;
namespace CovarianceExample
{
    interface IExtract<out T> { T Extract(); }
    class SampleClass<T> : IExtract<T>
    {
        private T data;
        public SampleClass(T data) {this.data = data;}   //ctor
        public T Extract()                               // Implementing interface
        {
            Console.WriteLine
                ("The type where the executing method is declared:\n{0}",
                this.GetType() );
            return this.data;
        }
    }
    class CovarianceExampleProgram
    {
        static void Main(string[] args)
        {
            SampleClass<string> sampleClassOfString = new SampleClass<string>("This is a string");
            IExtract<Object> iExtract = sampleClassOfString;

            // IExtract<object>.Extract() mapes to IExtract<string>.Extract()?
            object obj = iExtract.Extract();
            Console.WriteLine(obj);                  
            Console.ReadKey();
        }
    }
}

// Output:
// The type where the executing method is declared:
// CovarianceExample.SampleClass`1[System.String]
// This is a string

調用IExtract<object>.Extract()調用IExtract<string>.Extract() ,如輸出所示。 雖然我有點期待這種行為,但我無法告訴自己為什么它的行為方式如此。

IExtract<object>NOT在包含繼承層次IExtract<string> ,除了以下事實:C#制成IExtract<string>分配給IExtract<object >。 IExtract<string>根本沒有一個叫方法Extract()它繼承自IExtract<object>不像正常的繼承。 這個時候對我來說似乎沒有多大意義。

是否明智地說IExtract<string>OWN巧合(或設計)同樣名為Extract()方法隱藏了IExtract<object >的Extract()方法? 而且這是一種黑客攻擊? (選擇不好的詞!)

謝謝

你肯定對協方差如何運作有一些嚴重的誤解,但它並不是100%清楚我是什么。 我先說一下接口是什么,然后我們可以逐行查看你的問題並指出所有的誤解。

將接口視為“插槽”的集合,其中每個插槽都有一個合同 ,並包含一個滿足該合同的方法。 例如,如果我們有:

interface IFoo { Mammal X(Mammal y); }

那么IFoo只有一個插槽,那個插槽必須包含一個接收哺乳動物並返回哺乳動物的方法。

當我們隱式或顯式地將引用轉換為接口類型時,我們不會以任何方式更改引用。 相反,我們驗證引用類型是否具有該接口的有效槽表。 所以如果我們有:

class C : IFoo { 
  public Mammal X(Mammal y)
  {
    Console.WriteLine(y.HairColor);
    return new Giraffe();
  }
}

然后

C c = new C();
IFoo f = c;

可以認為C有一個小表,表示“如果C轉換為IFoo,則CX進入IFoo.X插槽。”

當我們將c轉換為f時,c和f具有完全相同的內容 它們是相同的參考 我們剛剛驗證了c是一種具有與IFoo兼容的槽表的類型。

現在讓我們來看看你的帖子。

調用IExtract<object>.Extract()調用IExtract<string>.Extract() ,如輸出所示。

讓我們清醒一點。

我們有sampleClassOfString ,它實現了IExtract<string> 它的類型有一個“槽表”,表示“我的Extract進入IExtract<string>.Extract的插槽”。

現在,當sampleClassOfString轉換為IExtract<object>時,我們還要進行檢查。 sampleClassOfString的類型是否包含適用於IExtract<object>的接口槽表? 是的確如此: 我們可以將現有的表用於IExtract<string>

為什么我們可以使用它,即使它們是兩種不同的類型? 因為所有合同仍然符合

IExtract<object>.Extract有一個契約:它是一個不帶任何東西並返回object 那么, IExtract<string>.Extract插槽中的方法符合該合同; 它不需要任何東西,它返回一個字符串,這是一個對象。

由於滿足了所有合同,我們可以使用我們已經獲得的IExtract<string>槽表。 賦值成功,所有調用都將通過IExtract<string>槽表。

IExtract<object> IExtract<string>包含IExtract<string>的繼承層次結構中

正確。

除了C#使IExtract<string>可分配給IExtract<object>這一事實。

不要混淆這兩件事; 他們不一樣。 繼承基類型的成員也是派生類型的成員的屬性。 賦值兼容性是可以將一種類型的實例分配給另一種類型的變量的屬性。 那些在邏輯上非常不同!

是的,存在連接,因為推導意味着賦值兼容性和繼承性 ; 如果D是基類型B的派生類型,那么D的實例可分配給類型B的變量, B的所有可遺傳成員都是D.的成員。

但不要混淆這兩件事; 僅僅因為它們是相關的並不意味着它們是相同的。 實際上有些語言不同; 也就是說,存在繼承與賦值兼容性正交的語言。 C#只是不是其中之一,你已經習慣了這樣一個世界,在這個世界中,繼承和賦值兼容性是如此緊密地聯系在一起,你從未學會將它們視為獨立的。 開始將它們視為不同的東西,因為它們是。

協方差是指將賦值兼容性關系擴展到不在繼承層次結構中的類型。 這就是協方差的意思 ; 如果在映射到泛型的過程中保留關系,則賦值兼容性關系是協變 “可以在需要水果的地方使用蘋果;因此,在需要一系列水果的情況下,可以使用一系列蘋果”是協方差 在映射到序列之間保留賦值兼容性關系。

IExtract<string>根本沒有一個名為Extract()的方法,它繼承自IExtract<object>

那是對的。 IExtract<string>IExtract<object>之間沒有任何繼承 但是,它們之間存在兼容性關系,因為符合IExtract<string>.Extract合同的任何方法 Extract 也是滿足IExtract<object>.Extract合同的方法。 因此,前者的時隙表可以在需要后者的情況下使用。

是否明智地說IExtract<string>的OWN巧合(或設計)同樣名為Extract()方法隱藏了IExtract<object>的Extract()方法?

絕對不。 沒有任何隱藏。 當衍生類型具有與基類型的繼承成員同名的成員時,會發生“隱藏”,並且新成員會隱藏舊成員以便在編譯時查找名稱 隱藏只是一個編譯時名稱查找概念; 它與接口在運行時的工作方式沒有任何關系。

而且這是一種黑客攻擊?

絕對沒有

我試圖找不到令人反感的建議,而且大多數都是成功的。 :-)

此功能由專家精心設計; 它是合理的(模數擴展到C#中現有的不健全性,例如不安全的數組協方差),並且它的實施非常謹慎和審查。 絕對沒有任何“hackish”它。

那么當我調用IExtract<object>.Extract()時會發生什么?

從邏輯上講,這是發生的事情:

將類引用轉換為IExtract<object> ,我們驗證引用中是否存在與IExtract<object>兼容的槽表。

當您調用Extract ,我們在已確定與IExtract<object>兼容的槽表中查找Extract槽的內容。 因為這是 IExtract<string>對象相同的槽表 ,所以同樣的事情發生了:類的Extract方法在那個槽中,所以它被調用。

在實踐中,情況稍微復雜一點; 在調用邏輯中有一堆齒輪可以確保在常見情況下的良好性能。 但從邏輯上講,您應該將其視為在表中查找方法,然后調用該方法。

代表也可以被標記為協變和逆變。 這是如何運作的?

從邏輯上講,您可以將委托視為只有一個名為“Invoke”的方法的接口,並從那里開始。 實際上,由於代理組合等問題,機制當然有所不同,但現在您可以看到它們的工作方式。

我在哪里可以了解更多?

這有點像消防:

https://stackoverflow.com/search?q=user%3A88656+covariance

所以我會從頂部開始:

協方差和對比方差之間的差異

如果您想要C#4.0中的功能歷史記錄,請從這里開始:

https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

請注意,這是在我們確定“in”和“out”作為逆變和協方差的關鍵詞之前編寫的。

在這里可以找到更多以“最新的第一”時間順序排列的文章:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

還有一些在這里:

https://ericlippert.com/category/covariance-and-contravariance/


練習:現在您已經大致了解了幕后的工作方式,您認為這有什么作用?

interface IFrobber<out T> { T Frob(); }
class Animal { }
class Zebra: Animal { }
class Tiger: Animal { }
// Please never do this:
class Weird : IFrobber<Zebra>, IFrobber<Tiger>
{
  Zebra IFrobber<Zebra>.Frob() => new Zebra();
  Tiger IFrobber<Tiger>.Frob() => new Tiger();
}
…
IFrobber<Animal> weird = new Weird();
Console.WriteLine(weird.Frob());

仔細考慮一下,看看你是否能解決所發生的事情。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM