[英]Understanding Covariant and Contravariant interfaces in C#
我在一本關於 C# 的教科書中遇到過這些,但我很難理解它們,可能是由於缺乏上下文。
是否有一個關於它們是什么以及它們在那里有用的簡潔解釋?
編輯以澄清:
協變接口:
interface IBibble<out T>
.
.
逆變接口:
interface IBibble<in T>
.
.
使用<out T>
,您可以將接口引用視為層次結構中的向上引用。
使用<in T>
,您可以將接口引用視為層次結構中的一個向下引用。
讓我試着用更多的英語來解釋它。
假設您要從動物園中檢索動物列表,並打算處理它們。 所有動物(在你的動物園里)都有一個名字和一個唯一的 ID。 有些動物是哺乳動物,有些是爬行動物,有些是兩棲動物,有些是魚,等等。但它們都是動物。
因此,通過您的動物列表(其中包含不同類型的動物),您可以說所有動物都有一個名稱,因此顯然獲得所有動物的名稱是安全的。
但是,如果您只有魚的清單,但需要像對待動物一樣對待它們,那行得通嗎? 直覺上,它應該可以工作,但在 C# 3.0 及之前,這段代碼將無法編譯:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
這樣做的原因是編譯器不“知道”您打算或可以在您檢索動物集合后對它做什么。 就它所知,可能有一種方法通過IEnumerable<T>
將對象放回列表中,這可能允許您將不是魚的動物放入應該只包含的集合中魚。
換句話說,編譯器不能保證這是不允許的:
animals.Add(new Mammal("Zebra"));
所以編譯器直接拒絕編譯你的代碼。 這就是協方差。
讓我們看看逆變。
既然我們的動物園可以處理所有的動物,它當然可以處理魚,所以讓我們嘗試在我們的動物園中添加一些魚。
在 C# 3.0 及之前,這不會編譯:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));
在這里,編譯器可以允許這段代碼,即使該方法返回List<Animal>
只是因為所有的魚都是動物,所以如果我們只是將類型更改為:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
然后它會起作用,但編譯器無法確定您沒有嘗試這樣做:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
由於該列表實際上是一個動物列表,這是不允許的。
因此,逆變和協變是您處理對象引用的方式以及您可以用它們做什么。
C# 4.0 中的in
和out
關鍵字專門將接口標記為一個或另一個。 使用in
,您可以將泛型類型(通常是 T)放在輸入位置,這意味着方法參數和只寫屬性。
使用out
,您可以將泛型類型放在output -positions 中,即方法返回值、只讀屬性和 out 方法參數。
這將允許您對代碼執行打算執行的操作:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe
List<T>
在List<T>
有入方向和出方向,因此它既不是協變也不是逆變,而是一個允許您添加對象的接口,如下所示:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
將允許你這樣做:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
以下是一些展示這些概念的視頻:
下面是一個例子:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
// We can do this since every Descendant is also a Base
// and there is no chance we can put Base objects into
// the returned object, since T is "out"
// We can not, however, put Base objects into b, since all
// Base objects might not be Descendant.
IBibbleOut<Base> b = GetOutDescendant();
// We can do this since every Descendant is also a Base
// and we can now put Descendant objects into Base
// We can not, however, retrieve Descendant objects out
// of d, since all Base objects might not be Descendant
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
沒有這些標記,以下內容可以編譯:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
或這個:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.