程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 詳解.NET 4.0中的泛型協變(covariant)和反變(contravariant)

詳解.NET 4.0中的泛型協變(covariant)和反變(contravariant)

編輯:C#入門知識

詳解.NET 4.0中的泛型協變(covariant)和反變(contravariant)。本站提示廣大學習愛好者:(詳解.NET 4.0中的泛型協變(covariant)和反變(contravariant))文章只能為提供參考,不一定能成為您想要的結果。以下是詳解.NET 4.0中的泛型協變(covariant)和反變(contravariant)正文


隨Visual Studio 2010 CTP表態的C#4和VB10,固然在支撐說話新特征方面走了相當紛歧樣的兩條路:C#側重增長前期綁定和與靜態說話相容的若干特征,VB10側重簡化說話和進步籠統才能;然則二者都增長了一項功效:泛型類型的協變(covariant)和反變(contravariant)。很多人對其懂得能夠僅限於增長的in/out症結字,而對其諸多特征有所不知。上面我們就對此停止一些具體的說明,贊助年夜家准確應用該特征。

配景常識:協變和反變

許多人能夠不不克不及很好地輿解這些來自於物理和數學的名詞。我們無需去懂得他們的數學界說,然則至多應當能分清協變和反變。現實上這個詞起源於類型和類型之間的綁定。我們從數組開端懂得。數組其實就是一種和詳細類型之間產生綁定的類型。數組類型Int32[]就對應於Int32這個本來的類型。任何類型T都有其對應的數組類型T[]。那末我們的成績就來了,假如兩個類型T和U之間存在一種平安的隱式轉換,那末對應的數組類型T[]和U[]之間能否也存在這類轉換呢?這就牽扯到了將本來類型上存在的類型轉換映照到他們的數組類型上的才能,這類才能就稱為“可變性(Variance)”。在.NET世界中,獨一許可可變性的類型轉換就是由繼續關系帶來的“子類援用->父類援用”轉換。舉個例子,就是String類型繼續自Object類型,所以任何String的援用都可以平安地轉換為Object援用。我們發明String[]數組類型的援用也繼續了這類轉換才能,它可以轉換成Object[]數組類型的援用,數組這類與原始類型轉換偏向雷同的可變性就稱作協變(covariant)。

因為數組不支撐反變性,我們沒法用數組的例子來說明反變性,所以我們如今就來看看泛型接口和泛型拜托的可變性。假定有如許兩個類型:TSub是TParent的子類,明顯TSub型援用是可以平安轉換為TParent型援用的。假如一個泛型接口IFoo<T>,IFoo<TSub>可以轉換為IFoo<TParent>的話,我們稱這個進程為協變,並且說這個泛型接口支撐對T的協變。而假如一個泛型接口IBar<T>,IBar<TParent>可以轉換為T<TSub>的話,我們稱這個進程為反變(contravariant),並且說這個接口支撐對T的反變。是以很好懂得,假如一個可變性和子類到父類轉換的偏向一樣,就稱作協變;而假如和子類到父類的轉換偏向相反,就叫反變性。你記住了嗎?

.NET 4.0引入的泛型協變、反變性

適才我們講授概念的時刻曾經用了泛型接口的協變和反變,但在.NET 4.0之前,不管C#照樣VB裡都不支撐泛型的這類可變性。不外它們都支撐拜托參數類型的協變和反變。因為拜托參數類型的可變性懂得起來籠統度較高,所以我們這裡禁絕備評論辯論。曾經完整可以或許懂得這些概念的讀者本身想必可以或許本身去懂得拜托參數類型的可變性。在.NET 4.0之前為何不許可IFoo<T>停止協變或反變呢?由於對接口來說,T這個類型參數既可以用於辦法參數,也能夠用於辦法前往值。假想如許的接口

Interface IFoo(Of T)
    Sub Method1(ByVal param As T)
    Function Method2() As T
End Interface
interface IFoo<T>
{
    void Method1(T param);
    T Method2();
}

假如我們許可協變,從IFoo<TSub>到IFoo<TParent>轉換,那末IFoo.Method1(TSub)就會釀成IFoo.Method1(TParent)。我們都曉得TParent是不克不及平安轉換成TSub的,所以Method1這個辦法就會變得不平安。異樣,假如我們許可反變IFoo<TParent>到IFoo<TSub>,則TParent IFoo.Method2()辦法就會釀成TSub IFoo.Method2(),本來前往的TParent援用未必可以或許轉換成TSub的援用,Method2的挪用將是不平安的。有此可見,在沒有額定機制的限制下,接口停止協變或反變都是類型不平安的。.NET 4.0改良了甚麼呢?它許可在類型參數的聲明時增長一個額定的描寫,以肯定這個類型參數的應用規模。我們看到,假如一個類型參數僅僅能用於函數的前往值,那末這個類型參數就對協變相容。而相反,一個類型參數假如僅能用於辦法參數,那末這個類型參數就對反變相容。以下所示:

Interface ICo(Of Out T)
    Function Method() As T
End Interface
 
Interface IContra(Of In T)
    Sub Method(ByVal param As T)
End Interface
interface ICo<out T>
{
    T Method();
}
 
interface IContra<in T>
{
    void Method(T param);
}

可以看到C#4和VB10都供給了年夜同小異的語法,用Out來描寫僅能作為前往值的類型參數,用In來描寫僅能作為辦法參數的類型參數。一個接口可以帶多個類型參數,這些參數可以既有In也有Out,是以我們不克不及簡略地說一個接口支撐協變照樣反變,只能說一個接口對某個詳細的類型參數支撐協變或反變。好比如有IBar<in T1, out T2>如許的接口,則它對T1支撐反變而對T2支撐協變。舉個例子來講,IBar<object, string>可以或許轉換成IBar<string, object>,這裡既有協變又有反變。

在.NET Framework中,很多接口都僅僅將類型參數用於參數或前往值。為了應用便利,在.NET Framework 4.0裡這些接口將從新聲明為許可協變或反變的版本。例如IComparable<T>便可以從新聲明成IComparable<in T>,而IEnumerable<T>則可以從新聲明為IEnumerable<out T>。不外某些接口IList<T>是不克不及聲明為in或out的,是以也就沒法支撐協變或反變。

上面提起幾個泛型協變和反變輕易疏忽的留意事項:

1.唯一泛型接口和泛型拜托支撐對類型參數的可變性,泛型類或泛型辦法是不支撐的。
2.值類型不介入協變或反變,IFoo<int>永久沒法釀成IFoo<object>,不論有沒有聲明out。由於.NET泛型,每一個值類型會生成專屬的關閉結構類型,與援用類型版本不兼容。
3.聲明屬性時要留意,可讀寫的屬性會將類型同時用於參數和前往值。是以只要只讀屬性才許可應用out類型參數,只寫屬機能夠應用in參數。

協變和反變的互相感化

這是一個相當風趣的話題,我們先來看一個例子:

Interface IFoo(Of In T)
 
End Interface
 
Interface IBar(Of In T)
    Sub Test(ByVal foo As IFoo(Of T)) '對嗎?
End Interface
interface IFoo<in T>
{
 
}
 
interface IBar<in T>
{
    void Test(IFoo<T> foo); //對嗎?
}

你能看出上述代碼有甚麼成績嗎?我聲清楚明了in T,然後將他用於辦法的參數了,一切正常。但出乎你料想的是,這段代碼是沒法編譯經由過程的!反而是如許的代碼經由過程了編譯:

Interface IFoo(Of In T)
 
End Interface
 
Interface IBar(Of Out T)
    Sub Test(ByVal foo As IFoo(Of T))
End Interface
interface IFoo<in T>
{
 
}
 
interface IBar<out T>
{
    void Test(IFoo<T> foo);
}

甚麼?明明是out參數,我們卻要將其用於辦法的參數才正當?初看起來切實其實會有一些驚異。我們須要費一些周折來懂得這個成績。如今我們斟酌IBar<string>,它應當可以或許協釀成IBar<object>,由於string是object的子類。是以IBar.Test(IFoo<string>)也就協釀成了IBar.Test(IFoo<object>)。當我們挪用這個協變前方法時,將會傳入一個IFoo<object>作為參數。想想,這個辦法是從IBar.Test(IFoo<string>)協變來的,所以參數IFoo<object>必需可以或許釀成IFoo<string>能力知足原函數的須要。這裡對IFoo<object>的請求是它可以或許反釀成IFoo<string>!而不是協變。也就是說,假如一個接口須要對T協變,那末這個接口一切辦法的參數類型必需支撐對T的反變。同理我們也能夠看出,假如接口要支撐對T反變,那末接口中辦法的參數類型都必需支撐對T協變才行。這就是辦法參數的協變-反變交換准繩。所以,我們其實不能簡略地說out參數只能用於前往值,它確切只能直接用於聲明前往值類型,然則只需一個支撐反變的類型協助,out類型參數就也能夠用於參數類型!換句話說,in參數除直接聲明辦法參數以外,也僅能借助支撐協變的類型能力用於辦法參數,僅支撐對T反變的類型作為辦法參數也是不許可的。要想深入懂得這一概念,第一次看能夠會有點繞,建議有前提的情形下多停止一些試驗。

適才提到了辦法參數上協變和反變的互相影響。那末辦法的前往值會不會有異樣的成績呢?我們看以下代碼:

Interface IFooCo(Of Out T)
 
End Interface
 
Interface IFooContra(Of In T)
 
End Interface
 
Interface IBar(Of Out T1, In T2)
    Function Test1() As IFooCo(Of T1)
    Function Test2() As IFooContra(Of T2)
End Interface
interface IFooCo<out T>
{
}
 
interface IFooContra<in T>
{
}
 
interface IBar<out T1, in T2>
{
    IFooCo<T1> Test1();
    IFooContra<T2> Test2();
}

我們看到和方才正好相反,假如一個接口須要對T停止協變或反變,那末這個接口一切辦法的前往值類型必需支撐對T異樣偏向的協變或反變。這就是辦法前往值的協變-反變分歧准繩。也就是說,即便in參數也能夠用於辦法的前往值類型,只需借助一個可以反變的類型作為橋梁便可。假如對這個進程還不是特殊清晰,建議也是寫一些代碼來停止試驗。至此我們發明協變和反變有很多風趣的特征,以致於在代碼裡in和out都不像他們字面意思那末好懂得。當你看到in參數湧現在前往值類型,out參數湧現在參數類型時,萬萬別暈倒,用本文的常識便可破解個中奇妙。

總結

經由本文的講授,年夜家應當曾經初步懂得的協變和反變的寄義,可以或許分清協變、反變的進程。我們還評論辯論了.NET 4.0支撐泛型接口、拜托的協變和反變的新功效和新語法。最初我們還套了論的協變、反變與函數參數、前往值的互相感化道理,和由此發生的奧妙寫法。我願望年夜家看了我的文章後,可以或許將這些常識用於泛型法式設計傍邊,准確應用.NET 4.0的新增功效。祝年夜家應用高興!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved