日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#泛谈 —— 变体(协变/逆变)

發(fā)布時間:2025/3/15 C# 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#泛谈 —— 变体(协变/逆变) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

有如下四個類。

public class Animal{}public class Mammal : Animal{}public class Dog : Mammal{public void EatBone(){}}public class Panda : Mammal{public void EatBamboo(){}}

?

Animal animal = new Dog();

這樣的賦值肯定是沒問題的,但這只是多態(tài)。

變體的大概意思是:有T和U兩個類型,并且T = U (此處的等號為賦值)成立,如果T和U經(jīng)過某種操作之后分別得到T’和U’,并且T’ = U’也成立,則稱此操作為協(xié)變;如果U’ = T’,則稱此操作為逆變。

//以下代碼能通過,則說明Operation是協(xié)變。
T = U; //=表示賦值↓ Operation(T) = Operation(U);

//類似的,以下操作為逆變。
T = U; ↓ Operation2(U) = Operation2(T);

?

一、特殊的協(xié)變——數(shù)組

我們常說協(xié)變和逆變是.net 4.0中引入的概念,但實際上并不是。其實只要符合上面定義的,都是變體。我們先來看一個.net 1.0中就包含的一個協(xié)變:

Animal[] animalArray = new Dog[10];

這個不是多態(tài),因為Dog[]的父類不是Animal[],而是object。
我們對照變體的定義來看一下,首先Animal = Dog,這個是成立的,因為Dog是Animal的子類;然后經(jīng)過Array這個操作后,等式左右兩邊分別變成了Animal[]和dog[],并且這個等式仍然成立。這已經(jīng)是滿足協(xié)變的定義了。

可能有人會困惑,這為什么等號就成立了呢?
我們有一點要明確的是,因為C#語言規(guī)定了Array操作是協(xié)變,并且Compiler支持了,所以等式就成立了。變體都是人為定的,你甚至可以規(guī)定任何操作都是協(xié)變或者逆變,無非就是使編譯和在運行期變體處的賦值通過。

我們再看一下Array的應(yīng)用:

Animal[] animalArray = new Dog[10]; //Line1animalArray[0] = new Bird(); //Line2

上面的代碼能編譯通過,Line1處也能運行通過,但是到了Line2處就會拋異常,所以說雖然Array這個操作是一個協(xié)變,但并不是安全的,在某些時候還是會出錯。

至于說為什么要支持Array這樣的協(xié)變,據(jù)Eric Lippert在Covariance and Contravariance in C#, Part Two: Array Covariance說,是為了兼容Java的語法,雖然他本人也不是很滿意這樣的設(shè)計。

?

二、委托中的變體

在.net 2.0中委托也支持了協(xié)變,不過暫時還只是支持方法的賦值。

考慮下面的代碼

//一個入?yún)镈og的委托。抓住了一只Dog,應(yīng)該怎么處理?delegate void DogCatched(Dog d); //定義兩個方法void OnAnimalCatched(Animal animal) {} //處理抓到的Animalvoid OnDogCatched(Dog dog) {} //處理抓到的Dog Catch catchDog = OnDogCatched; //把抓到的Dog交給處理Dog的方法catchDog = OnAnimalCatched; //把抓到的Dog交給處理Animal的方法

以上兩個賦值都可以成功,其中第一個為符合委托原型的賦值。第二個則可以看做是Operate(Dog) = Operate(Animal),那這是一個逆變。

同樣的,下面就是一個協(xié)變。

//一個返回值為Animal的委托,一個需要抓到一只Animal的任務(wù)delegate Animal AnimalCatching();//兩個方法Animal CatchAnAnimal() { return new Animal(); } //抓到一個AnimalDog CatchADog() { return new Dog(); } //抓到一個Dog AnimalCatching animalCatching = CatchAnAnimal; //把抓Animal的任務(wù)交給能抓到Animal的方法animalCatching = CatchADog; //把抓Animal的任務(wù)交給能抓到Dog的方法

?

至于Action<T>和Func<TResult>(.net 3.5)等泛型委托,其實也是如此,同樣只局限于方法給委托實例賦值,而不支持委托實例賦值給委托實例。下面的例子編譯時會報錯。

Action<Animal> aa = animal => { };Action<Dog> ad = aa; //編譯錯誤

?

三、泛型中的變體

我們常說的協(xié)變和逆變,大多數(shù)指的是.net 4.0中引入的針對泛型委托和泛型接口的變體。

泛型委托

?我們發(fā)現(xiàn),到了.net 4.0,之前不能編譯的這段代碼通過了

Action<Animal> aa = animal => { };Action<Dog> ad = aa; //編譯通過

?

其實是Action的簽名變了,多了in這個關(guān)鍵字。

public delegate void Action<T>(T obj); //.net 4 之前public delegate void Action<in T>(T obj); //.net 4

?

類似的,Func的簽名也變了,多了out關(guān)鍵字

public delegate TResult Func<TResult>(); //.net 4 之前public delegate TResult Func<out TResult>(); //.net 4

in和out就是C# 4.0中用于在泛型中顯式的表示協(xié)變和逆變的關(guān)鍵字。in表示逆變參數(shù),out表示協(xié)變參數(shù)。

對于泛型委托的變體這一塊上,.net 4.0相對于之前的版本主要增強的就是委托實例賦值委托實例(方法賦值給委托實例是.net 2.0就支持的)。

泛型接口

在.net 4.0以前,Array是協(xié)變的(盡管它不安全),但IList<T>卻不是,IEnumerable<T>也不是。而到了.net 4.0,我們終于可以這樣干了:

IEnumerable<Animal> animals = new List<Dog>(); //.net 4正確

?

不過以下的操作還是會造成編譯失敗:

IList<Animal> a2 = new List<Dog>(); //錯誤

?

究其原因,當然還是因為IEnumerable<T>在.net 4.0中是協(xié)變的,IList<T>不是:

public interface IEnumerable<out T> : IEnumerablepublic interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

?

那泛型接口既然有協(xié)變的,同樣也有逆變的,如IComparable<T>。

?

四、一些疑問

1,問:我們自定義的泛型接口和泛型委托是否可以隨便加上in/out關(guān)鍵字,來表明它是逆變或者協(xié)變的?

答:這個當然是不可能的,編譯器會校驗。

一般來說,如果一個泛型接口中,所有用到T的方法,都只將其用于輸入?yún)?shù),則T可以是逆變參數(shù);如果用到T的方法,都只將其用于返回值,則T可以是協(xié)變參數(shù)。

委托的輸入?yún)?shù)可以是逆變參數(shù);返回值可以是協(xié)變參數(shù)。

2,問:既然in/out不能亂加,為什么還要加呢?完全由編譯器來決定協(xié)變或者逆變的賦值不可以么?

答:這個理論上應(yīng)該是可以的,不過in/out關(guān)鍵字就像是一個泛型委托和泛型接口定義者同使用者之間的契約,必須顯式的指定使用方式,否則,程序中出現(xiàn)一些既不是多態(tài),又沒有標明是協(xié)變或逆變,卻可以賦值成功的代碼,看起來比較混亂。

3,問:是不是所有的泛型委托和接口都遵從輸入?yún)?shù)是協(xié)變的,輸出參數(shù)是逆變的這一規(guī)律呢?

答:我們定義一個泛型委托Operate<T>,它的輸入?yún)?shù)是一個Action<T>

delegate void Operate<T>(Action<T> action); //兩個Action<T>的實例
Action<Mammal> MammalEat = mammal => Console.WriteLine("mammal eat");Action<Panda> PandaEat = panda => panda.EatBamboo();
//Operate<T>的實例Operate
<Mammal> MammalOperation = action => action(new Dog()); //Action<T>是逆變,所以這里是允許的。

然后我們可以執(zhí)行下面的操作

//操作1
MammalOperation(MammalEat);

如果我們想讓這個泛型委托是一個變體,按照我們通常的理解,T是用作輸入?yún)?shù)的,那肯定就是逆變,應(yīng)該加上in關(guān)鍵字。我們不考慮編譯器的提示,假設(shè)定義成這樣:

delegate void Operate<in T>(Action<T> action);

因為是逆變,所以,我們可以將Operate<Mammal>賦給Operate<Panda>

Operate<Panda> PandaOperate = MammalOperation;

由于上面這個Operate的T已經(jīng)改成了Panda,所以其對應(yīng)參數(shù)Action的T也應(yīng)該改為Panda,所以上面的“操作1”可以改成這樣:

//操作2MammalOperation(PandaEat);

最終變成了PandaOperate = (new Dog()).EatBamboo()。這是個啥?完全不合常理。

實際上,當我們給Operate<T>加上in的時候,編譯器就已經(jīng)告訴我們,這是不對的了。寫成out就可以了,說明這是一個協(xié)變,下面的操作也是可以的:

?

Operate<Animal> AnimalOperate = MammalOperation;

?

上面這個例子似乎說明了,也并不是所有的輸入?yún)?shù)都是逆變的?其實這已經(jīng)不完全是一個輸入?yún)?shù)了,由于有Action<T>的影響,似乎就變成了“逆逆得協(xié)”?如果把Action<T>換成Func<T>,則Operate<T>就應(yīng)該用in關(guān)鍵字了。是不是比較費腦?還好平時工作中很少碰到這種情況,更何況還有編譯器給我們把關(guān)。

?

以上內(nèi)容參考自Eric Lippert的Covariance and Contravariance In C#系列,對.net中協(xié)變逆變的進化做了很詳細的描述,有興趣可以看一下。

?

轉(zhuǎn)載于:https://www.cnblogs.com/joecheung/p/3139124.html

總結(jié)

以上是生活随笔為你收集整理的C#泛谈 —— 变体(协变/逆变)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。