根本技術:Visual Basic 2010中的泛型協變和逆變。本站提示廣大學習愛好者:(根本技術:Visual Basic 2010中的泛型協變和逆變)文章只能為提供參考,不一定能成為您想要的結果。以下是根本技術:Visual Basic 2010中的泛型協變和逆變正文
Visual Studio 2010 有一項名為泛型協變和逆變的新功用,可以用來處置泛 型接口和委托。在 Visual Studio 2010 和 Microsoft .NET Framework 4 之前 的版本中,在子類型化方面,泛型的行為是 不變的,因而類型參數不同的泛型類 型不能互相轉換。
例如,假如嘗試將 List(Of Derived) 傳遞給承受 IEnumerable(Of Base) 的 辦法,將會呈現錯誤。 但 Visual Studio 2010 可以處置類型平安的協變和逆變 ,類型平安的協變和逆變支持泛型接口和委托類 型上的協變和逆變類型參數聲明 。在本文中,我將詳細討論這一功用,並引見如何在使用順序中運用這一 功用。
由於按鈕是控件,依據根本的面向對象承繼准繩,您能夠以為上面的代碼可以 正常運轉:
Dim btnCollection As IEnumerable(Of Button) = New List (Of Button) From {New Button}
Dim ctrlCollection As IEnumerable(Of Control) = btnCollection
雖然這在 Visual Studio 2008 中是不允許的,並會給出錯誤 “IEnumerable(Of Button) 無法轉換為 IEnumerable(Of Control)” 。但作為面向對象編程人員,我們知道 Button 類型的值可以轉換為控件, 因而 如前所述,依據根本承繼准繩,這段代碼應該是可以運用的。
請看上面的例子:
Dim btnCollection As IList(Of Button) = New List(Of Button) From {New Button}
Dim ctrlCollection As IList(Of Control) = btnCollection
ctrlCollection(0) = New Label
Dim firstButton As Button = btnCollection(0)
這段代碼會失敗並引發 InvalidCastException,由於編程人員將 IList(Of Button) 轉換為 IList (Of Control),然後向其中拔出一個完全不是按鈕的控件 。
Visual Studio 2010 可以辨認並允許相似第一個示例的代碼,但在面向 .NET Framework 4 時,仍可 以不允許相似第二個示例的代碼。關於大少數用戶,在大 局部時間裡,順序將以預期的方式運轉,不需求 進一步深究。但在本文中,我將 進一步闡明這些代碼如何才干正常運轉以及其中的緣由。
協變
第一段代碼將 IEnumerable(Of Button) 視為 IEnumerable(Of Control),為 什麼在 Visual Studio 2010 中這是代碼平安的?第二個代碼示例將 IList(Of Button) 視為 IList(Of Control),為什麼這不 是平安的?
第一段代碼是平安的,緣由在於 IEnumerable(Of T) 是“輸入” 接口,也就是說,在 IEnumerable (Of Control) 中,接口用戶只是從列表中讀 取控件。
第二段代碼不是平安的,緣由在於,IList(Of T) 是“輸出輸入” 接口,因而在 IList(Of Control) 中,接口用戶可以讀取控件,也可以寫入控件 。
Visual Studio 2010 支持這種代碼的新言語功用稱為泛型協變。在 .NET Framework 4 中, Microsoft 重新編寫了框架的以下代碼行:
Interface IEnumerable(Of Out T)
...
End Interface
Interface IList(Of T)
...
End Interface
IEnumerable(Of Out T) 中的 Out 批注指示,假如 IEnumerable 中無方法使 用到 T,T 只能用在輸 出地位,如函數前往的輸入地位或只讀屬性類型的輸入位 置。這樣,用戶可以將恣意 IEnumerable(Of Derived) 轉換為 IEnumerable(Of Base),而不會引發 InvalidCastException。
IList 沒有批注,這是由於 IList(Of T) 是輸出輸入接口。這樣,用戶不能 將 IList(Of Derived) 轉換為 IList(Of Base),反之亦然;如前所述,這樣會 引發 InvalidCastException。
逆變
Out 批注有一個絕對的批注。這並不容易闡明,我先舉一個例子:
Dim _compOne As IComparer(Of Control) = New MyComparerByControlName()
Dim _compTwo As IComparer(Of Button) = _compOne
Dim btnOne = new Button with {.Name = "btnOne", OnClick = AddressOf btnOneClick, Left=20}
Dim btnTwo = new Button with {.Name = "btnTwo", OnClick = AddressOf btnTwoClick, Left=100}
Dim areSame = _compTwo.Compare(btnOne, btnTwo)
這裡我創立了一個可以確定恣意兩個控件能否相反的比擬器,比擬辦法是只查 看它們的稱號。
由於該比擬器可以比擬恣意控件,它當然能比擬兩個恰恰都為按鈕的控件。這 就是它可以平安地轉換 為 IComparer(Of Button) 的緣由。通常,可以將 IComparer(Of Base) 平安地轉換為恣意 IComparer (Of Derived)。這稱為逆變 。這是經過與 Out 批注絕對的 In 批注完成的。
.NET Framework 4 也停止了修正以引入 In 泛型類型參數:
Interface IComparer(Of In T)
...
End Interface
由於 IComparer(Of T) In 批注的存在,IComparer 中用到 T 的每個辦法都 只在輸出地位運用 T,如 ByVal 參數的輸出地位或只寫屬性類型的輸出地位。這 樣,用戶可以將 IComparer(Of Base) 轉換為恣意 IComparer(Of Derived),而 不會引發 InvalidCastException。
上面我們看一個 .NET 4 中的 Action 委托示例,該委托在 T 中成為逆變的 :
Dim actionControl As Action(Of Control)
Dim actionButton As Action(Of Button) = actionControl
此示例在 .NET 4 中運轉正常,由於委托 actionButton 的用戶將一直運用 Button 參數調用它,而 Button 參數是控件。
您也可以向自己的泛型接口和委托添加 In 和 Out 批注。但由於公共言語運 行時 (CLR) 限制,不能 對類、構造或別的對象運用這些批注。簡言之,只要接 口和委托可以是協變或逆變的。
聲明/語法
Visual Basic 運用兩個新的上下文關鍵字:引入協變的 Out 和引入逆變的 In,如下例所示:
Public Delegate Function Func(Of In TArg, Out TResult) (ByVal arg As TArg) As TResult
Public Interface IEnumerable(Of Out Tout)
Inherits IEnumerable
Function GetEnumerator() As IEnumerator(Of Tout)
End Interface
Public Interface IEnumerator(Of Out Tout)
Inherits IEnumerator
Function Current() As Tout
End Interface
Public Interface IComparer(Of In Tin)
Function Compare(ByVal left As Tin, ByVal right As Tin) As Integer
End Interface
但是,究竟為什麼需求這兩個上下文關鍵字或這種語法?為什麼不自動推斷變 化 In/Out 呢?首先, 這對編程人員聲明其意圖十分有用。其次,有些時分編譯 器不能自動推斷最佳變化批注。
讓我們看一看 IReadWriteBase 和 IReadWrite 這兩個接口:
Interface IReadWriteBase(Of U)
Function ReadWrite() As IReadWrite(Of U)
End Interface
Interface IReadWrite(Of T) : Inherits IReadWriteBase(Of T)
End Interface
假如編譯器將它們都推斷為 Out,如下所示,代碼會正常運轉:
Interface IReadWriteBase(Of Out U)
Function ReadWrite() As IReadWrite(Of U)
End Interface
Interface IReadWrite(Of Out T)
Inherits IReadWriteBase(Of T)
End Interface
假如編譯器將它們都推斷為 In,如下所示,代碼也會正常運轉:
Interface IReadWrite(Of In T)
Inherits IReadWriteBase(Of T)
End Interface
Interface IReadWriteBase(Of In U)
Function ReadWrite() As IReadWrite(Of U)
End Interface
編譯器不知道該推斷為 In 還是 Out,因而提供了一種語法。
Out/In 上下文關鍵字只用在接口和委托聲明中。在任何其他泛型參數中運用 這兩個關鍵字都將招致編 譯時錯誤。Visual Basic 編譯器不允許變體接口包括 嵌套枚舉、類和構造,由於 CLR 不支持變體類。不 過可以在類中嵌套變體接口 。
處置多義性
協變和逆變會在成員查詢中形成多義性,因而,應該知道是什麼惹起多義性以 及 Visual Basic 如何 處置多義性。
讓我們看看圖 1 中的示例,在該示例中,我們嘗試將 Comparer 轉換為 IComparer(Of Control),其 中 Comparer 完成 IComparer(Of Button) 和 IComparer(Of CheckBox)。
圖 1 不明白的轉換
Option Strict On
Imports System.Windows.Forms
Interface IComparer(Of Out Tout)
End Interface
Class Comparer
Implements IComparer(Of Button)
Implements IComparer(Of CheckBox)
End Class
Module VarianceExample
Sub Main()
Dim iComp As IComparer(Of Control) = New Comparer()
End Sub
End Module
由於 IComparer(Of Button) 和 IComparer(Of CheckBox) 都可以變體轉換為 IComparer(Of Control),所以轉換不明白。因而,Visual Basic 編譯器依據 CLR 規則查找不明白轉換,假如 Option Strict 為 On,則不允許在編譯時停止 這樣的不明白轉換;假如 Option Strict 為 Off,則編譯器生成 正告。
圖 2 中的轉換在運轉時能成功執行,不會生成編譯時錯誤。
圖 2 運轉時成功的轉換
Option Strict On
Imports System.Windows.Forms
Interface IEnumerable(Of Out Tout)
End Interface
Class ControlList
Implements IEnumerable(Of Button)
Implements IEnumerable(Of CheckBox)
End Class
Module VarianceExample
Sub Main()
Dim _ctrlList As IEnumerable(Of Control) = CType(New ControlList, IEnumerable(Of Button))
Dim _ctrlList2 As IEnumerable(Of Control) = CType(New ControlList, IEnumerable(Of CheckBox))
End Sub
End Module
圖 3 闡明了完成 IComparer(of IBase) 和 IComparer(of IDerived) 泛型接 口的風險性。這裡, Comparer1 和 Comparer2 類運用不同的泛型類型參數以不 同的順序完成相反的變體泛型接口。雖然 Comparer1 和 Comparer2 在完成該接 口時除排序外都相反,但在這些類中調用 Compare 辦法會失掉不同 的後果。
圖 3 同一辦法失掉的不同後果
Option Strict Off
Module VarianceExample
Sub Main()
Dim _comp As IComparer(Of Account) = New Comparer1()
Dim _comp2 As IComparer(Of Account) = New Comparer2()
Dim _account = New Account With {.AccountType = "Checking", .IsActive = True}
Dim _account2 = New Account With {.AccountType = "Saving", .IsActive = False}
‘// Even though _comp and _comp2 are *IDENTICAL*, they give different results!
Console.WriteLine(_comp.Compare(_account, _account2)) ‘; // prints 0
Console.WriteLine(_comp2.Compare(_account, _account2)) ‘; // prints - 1
End Sub
Interface IAccountRoot
Property AccountType As String
End Interface
Interface IAccount
Inherits IAccountRoot
Property IsActive As Boolean
End Interface
Class Account
Implements IAccountRoot, IAccount
Public Property AccountType As String Implements IAccountRoot.AccountType
Public Property IsActive As Boolean Implements IAccount.IsActive
End Class
Class Comparer1
Implements IComparer(Of IAccountRoot), IComparer(Of IAccount)
Public Function Compare(ByVal x As IAccount, ByVal y As IAccount) As Integer Implements System.Collections.Generic.IComparer(Of IAccount).Compare
Dim c As Integer = String.Compare(x.AccountType, y.AccountType)
If (c <> 0) Then
Return c
Else
Return (If(x.IsActive, 0, 1)) - (If(y.IsActive, 0, 1))
End If
End Function
Public Function Compare(ByVal x As IAccountRoot, ByVal y As IAccountRoot) As Integer Implements System.Collections.Generic.IComparer(Of IAccountRoot).Compare
Return String.Compare(x.AccountType, y.AccountType)
End Function
End Class
Class Comparer2
Implements IComparer(Of IAccount), IComparer(Of IAccountRoot)
Public Function Compare(ByVal x As IAccount, ByVal y As IAccount) As Integer Implements System.Collections.Generic.IComparer(Of IAccount).Compare
Dim c As Integer = String.Compare(x.AccountType, y.AccountType)
If (c <> 0) Then
Return c
Else
Return (If(x.IsActive, 0, 1)) - (If(y.IsActive, 0, 1))
End If
End Function
Public Function Compare(ByVal x As IAccountRoot, ByVal y As IAccountRoot) As Integer Implements System.Collections.Generic.IComparer(Of IAccountRoot).Compare
Return String.Compare(x.AccountType, y.AccountType)
End Function
End Class
End Module
即便 _comp 和 _comp2 相反,圖 3 中的代碼也會發生不同的後果,緣由如下 。編譯器只收回執行轉 換的 Microsoft 兩頭言語。因而,假如 Compare() 辦法 的完成不同,獲取 IComparer(Of IAccountRoot) 還是 IComparer(Of IAccount) 接口由 CLR 停止選擇,CLR 總是選擇接口列表中第一個 賦值兼容的接口。因而 經過圖 3 中的代碼,Compare() 辦法會失掉不同的後果,由於 CLR 為 Comparer1 類選擇 IComparer(Of IAccountRoot) 接口,為 Comparer2 類選擇 IComparer(Of IAccount) 接口。
泛型接口約束
編寫泛型約束 (Of T As U, U) 時,如今除承繼外還包括了變化可轉換性。圖 4 演示 (of T As U, U) 包括變化可轉換性。
圖 4 泛型約束如何包括變化可轉換性
Option Strict On
Imports System.Windows.Forms
Module VarianceExample
Interface IEnumerable(Of Out Tout)
End Interface
Class List(Of T)
Implements IEnumerable(Of T)
End Class
Class Program
Shared Function Foo(Of T As U, U)(ByVal arg As T) As U
Return arg
End Function
Shared Sub Main()
‘This is allowed because it satisfies the constraint Button AS Control
Dim _ctrl As Control = Foo(Of Button, Control)(New Button)
Dim _btnList As IEnumerable(Of Button) = New List (Of Button)()
‘This is allowed because it satisfies the constraint IEnumerable(Of Button) AS IEnumerable(Of Control)
Dim _ctrlCol As IEnumerable(Of Control) = Foo(Of IEnumerable(Of Button), IEnumerable(Of Control))(_btnList)
End Sub
End Class
End Module
變體泛型參數可以約束為不同的變體參數。請看:
Interface IEnumerable(Of In Tin, Out Tout As Tin)
End Interface
Interface IEnumerable(Of Out Tout, In Tin As Tout)
End Interface
在本例中,在滿足約束的狀況下,IEnumerator(Of ButtonBase, ButtonBase) 可以變化轉換為 IEnumerator(Of Control, Button),IEnumerable(Of Control, Button) 可以轉換為 IEnumerable(Of ButtonBase, ButtonBase)。從實際上說, 它可以進一步變化轉換為 IEnumerable(Of ButtonBase, Control),但這樣就不 再滿足約束,因而不是無效類型。圖 5 顯示了一個先進先出的對象集合,在該集 合中,約束十分有用。
圖 5 約束十分有用的對象集合
Option Strict On
Imports System.Windows.Forms
Interface IPipe(Of Out Tout, In Tin As Tout)
Sub Push(ByVal x As Tin)
Function Pop() As Tout
End Interface
Class Pipe(Of T)
Implements IPipe(Of T, T)
Private m_data As Queue(Of T)
Public Function Pop() As T Implements IPipe(Of T, T).Pop
Return m_data.Dequeue()
End Function
Public Sub Push(ByVal x As T) Implements IPipe(Of T, T).Push
m_data.Enqueue(x)
End Sub
End Class
Module VarianceDemo
Sub Main()
Dim _pipe As New Pipe(Of ButtonBase)
Dim _IPipe As IPipe(Of Control, Button) = _pipe
End Sub
End Module
在圖 5 中,假如提供 _IPipe,只能將 Button 推入管道,並且只能從中讀取 Control。請留意,可 以將變體接口約束為值類型,這樣該接口決不允許變化轉 換。上面是泛型參數中的值類型約束的示例:
Interface IEnumerable(Of Out Tout As Structure)
End Interface
對值類型停止約束能夠毫無用途,由於運用值類型實例化的變體接口不能停止 變化轉換。不過請留意 ,當 Tout 是構造時,經過約束直接推斷類型能夠十分有 用。
函數的泛型參數約束
辦法/函數中的約束必需是 In 類型。上面是關於函數泛型參數約束的兩種基 本研討辦法。
大少數狀況下,函數的泛型參數根本上是函數的輸出,一切輸出都必需是 In 類型。
客戶端可以將恣意 Out 類型變化轉換為 System.Object。假如泛型參數約束 是某種 Out 類型,客戶 端實踐上就可以刪除該約束,而約束不該如此,
讓我們看看圖 6,該圖清楚地闡明了約束沒有此變化無效性規則時的狀況。
圖 6 約束沒有變化無效性規則時的狀況
Option Strict On
Imports System.Windows.Forms
Interface IEnumerable(Of Out Tout)
Sub Foo(Of U As Tout)(ByVal arg As U)
End Interface
Class List(Of T)
Implements IEnumerable(Of T)
Private m_data As t
Public Sub Foo(Of U As T)(ByVal arg As U) Implements IEnumerable(Of T).Foo
m_data = arg
End Sub
End Class
Module VarianceExample
Sub Main()
‘Inheritance/Implements
Dim _btnCollection As IEnumerable(Of Button) = New List(Of Button)
‘Covariance
Dim _ctrlCollection As IEnumerable(Of Control) = _btnCollection
‘Ok, Constraint-satisfaction, because Label is a Control
_ctrlCollection.Foo(Of Label)(New Label)
End Sub
End Module
在圖 6 中,Label 在 m_data 中存儲為 Button,這是合法的。因而,辦法/ 函數中的約束必需是 In 類型。
重載解析
重載是指創立承受不同參數類型的多個同名函數。重載解析是從一組備選函數 中選擇最佳函數的編譯 機遇制。
讓我們看看上面的示例:
Private Overloads Sub Foo(ByVal arg As Integer)
End Sub
Private Overloads Sub Foo(ByVal arg As String)
End Sub
Foo(2)
後台終究發作了什麼?當編譯器發現 Foo(2) 調用時,它必需指出要調用哪個 Foo。為此,它運用了 以下復雜算法:
經過查找一切名為 Foo 的函數,生成一個適用候選函數集。在我們的示例中 ,有兩個函數可以思索。
檢查每個候選函數的參數,移除不適用的函數。請留意,編譯器還執行一定的 泛型驗證和類型推斷。
隨著變化轉換的引入,一組預定義轉換失掉了擴展,因而,步驟 2 將承受比 以前更多的候選函數。此 外,在常呈現的存在兩個異樣特定的候選函數的狀況下 ,編譯器會選擇未隱藏的候選函數,但如今,隱藏 的候選函數能夠范圍更寬,因 此,編譯器將改為選擇隱藏的候選函數。圖 7 演示在 Visual Basic 中添 加變 化轉換能夠會被毀壞的代碼。
圖 7 在 Visual Basic 中添加變化轉換能夠會毀壞的代碼
Option Strict On
Imports System.Windows.Forms
Imports System.Collections.Generic
Module VarianceExample
Sub Main()
Dim _ctrlList = New ControlList(Of Button)
‘Picks Add(ByVal f As IEnumerable(Of Control)), Because of variance- convertibility
_ctrlList.Add(New ControlList(Of Button))
End Sub
End Module
Interface IEnumerable(Of Tout)
End Interface
Class ControlList(Of T)
Implements IEnumerable(Of T)
Sub Add(ByVal arg As Object)
End Sub
Sub Add(ByVal arg As IEnumerable(Of Control))
End Sub
End Class
在 Visual Studio 2008 中,調用 Add 可以綁定到 Object,但由於 Visual Studio 2010 支持變化 轉換,我們改為運用 IEnumerable(Of Control)。
僅當沒有其他候選函數時,編譯器才會選擇減少候選函數,但假如因變化可轉 換性而有新的拓寬候選 函數,編譯器將轉為選擇拓寬候選函數。假如因變化可轉 換性呈現了另一個新的減少候選函數,編譯器將 收回錯誤。
擴展辦法
運用擴展辦法可以向現有類型添加辦法,而不用創立新的派生類型,也不用重 新編譯或修正原始類型 。在 Visual Studio 2008 中,擴展辦法支持數組協變, 如下例所示:
Option Strict On
Imports System.Windows.Forms
Imports System.Runtime.CompilerServices
Module VarianceExample
Sub Main()
Dim _extAdd(3) As Button ‘Derived from Control
_extAdd.Add()
End Sub
<Extension()>
Public Sub Add(ByVal arg() As Control)
System.Console.WriteLine(arg.Length)
End Sub
但在 Visual Studio 2010 中,擴展辦法還對泛型變化停止調度。這是一項重 要更改,如圖 8 所示, 由於這樣會有比以前更多的擴展候選函數。
圖 8 重要更改
Option Strict On
Imports System.Runtime.CompilerServices
Imports System.Windows.Forms
Module VarianceExample
Sub Main()
Dim _func As Func(Of Button) = Function() New Button
‘This was a compile-time error in VB9, But in VB10 because of variance convertibility, the compiler uses the extension method.
_func.Add()
End Sub
<Extension()> _
Public Sub Add(ByVal this As Func(Of Control))
Console.WriteLine(“A call to func of Control”)
End Sub
End Module
用戶定義的轉換
運用 Visual Basic 可以對類或構造聲明轉換,這樣就可以像根本類型一樣, 將它們與其他類和構造 相互轉換。在 Visual Studio 2010 中,變化可轉換性已 添加到用戶定義的轉換算法中。因而,每種用戶 定義的轉換的范圍會自動添加, 這能夠會形成毀壞。
由於 Visual Basic 和 C# 不允許在接口上停止用戶定義的轉換,我們只需關 注委托類型。思索圖 9 中的轉換,該轉換在 Visual Studio 2008 中可正常運轉 ,但在 Visual Studio 2010 會發生錯誤。
圖 9 在 Visual Studio 2008 中正常但在 Visual Studio 2010 會發生錯誤 的轉換
Option Strict On
Imports System.Windows.Forms
Module VarianceExample
Class ControlList
Overloads Shared Widening Operator CType(ByVal arg As ControlList) As Func(Of Control)
Console.WriteLine("T1->Func(Of Control)")
Return Function() New Control
End Operator
End Class
Class ButtonList
Inherits ControlList
Overloads Shared Widening Operator CType(ByVal arg As ButtonList) As Func(Of Button)
Console.WriteLine("T2->Func(Of Button)")
Return Function() New Button
End Operator
End Class
Sub Main()
'The conversion works in VB9 using ButtonList- >ControlList->Func(Of Control)
'Variance ambiguity error in VB10, because there will be another widening path (ButtonList-->Func(Of Button)-- [Covariance]-->Func(Of Control)
Dim _func As Func(Of Control) = New ButtonList
End Sub
End Module
圖 10 是另一個轉換示例,假如在 Visual Studio 2008 中設置 Option Strict On,則會招致編譯時 錯誤,但在支持變化轉換的 Visual Studio 2010 中可以成功運轉。
圖 10 Visual Studio 2010 經過支持變化轉換允許停止以前合法的轉換
Option Strict On
Imports System.Windows.Forms
Module VarianceExample
Class ControlList
Overloads Shared Narrowing Operator CType(ByVal arg As ControlList) As Func(Of Control)
Return Function() New Control
End Operator
Overloads Shared Widening Operator CType(ByVal arg As ControlList) As Func(Of Button)
Return Function() New Button
End Operator
End Class
Sub Main()
‘This was an error in VB9 with Option Strict On, but the conversion will succeed in VB10 using Variance->Func(Of Button)-[Covariance]-Func(Of Control)
Dim _func As Func(Of Control) = New ControlList
End Sub
End Module
Option Strict Off 的效果
Option Strict Off 通常允許減少轉換隱式停止。但無論 Option Strict 是 On 還是 Off,變化可轉 換性都需求其泛型參數經過 CLR 的賦值兼容拓寬相關; 經過減少相關是不夠的(請參閱圖 11)。留意: 假如存在 U->T 變化轉換, 我們以為 T->U 為減少轉換,假如 T->U 不明白,我們以為 T- >U 為 減少轉換。
圖 11 設置有 Option Strict Off
Option Strict Off
Imports System.Windows.Forms
Module VarianceExample
Interface IEnumerable(Of Out Tout)
End Interface
Class ControlList(Of T)
Implements IEnumerable(Of T)
End Class
Sub Main()
‘No compile time error, but will throw Invalid Cast Exception at run time
Dim _ctrlList As IEnumerable(Of Button) = New ControlList(Of Control)
End Sub
End Module
協變和逆變約束
上面列出了協變和逆變約束:
In/Out 上下文關鍵字只能在接口聲明或委托聲明中運用。在任何其他泛型參 數聲明中運用這兩個關鍵 字都將招致編譯時錯誤。變體接口不能嵌套類或構造, 但可以包括嵌套接口和嵌套委托,它們將從包括類 型停止變化轉換。
只要接口和委托可以是協變或逆變的,並且僅當類型參數為援用類型時才干是 協變或逆變的。
關於運用值類型實例化的變體接口,不能停止變化轉換。
枚舉、類、事情和構造不能進入變體接口。這是由於這些類/構造/枚舉是泛型 的,承繼其容器的泛型 參數,因此也承繼了其容器的變化轉換。CLI 標准不允許 運用變體類/構造/枚舉。
更靈敏更清潔的代碼
您能夠曾經知道,在運用泛型時,有些狀況下,假如支持協變和逆變,就可以 編寫更復雜或更清潔的 代碼。如今,Visual Studio 2010 和 .NET Framework 4 曾經完成了這些功用,經過對泛型接口和委托 中的類型參數聲明變化屬性,可以 使代碼更清潔、更靈敏。
為了協助完成這個目的,在 .NET Framework 4 中,IEnumerable 在其類型參 數中運用 Out 修飾符聲 明為協變接口,IComparer 運用 In 修飾符聲明為逆變 接口。因而,為使 IEnumerable(Of T) 可以變化 轉換為 IEnumerable(Of U), 必需契合下列條件之一:
T 承繼自 U
T 可變化轉換為 U
T 有任何其他類型的預定義 CLR 援用轉換
在 Basic 類庫中,這些接口的聲明如下:
Interface IEnumerable(Of Out T)
Function GetEnumerator() As IEnumerator(Of T)
End Interface
Interface IEnumerator(Of Out T)
Function Current() As t
End Interface
Interface IComparer(Of In T)
Function Compare(ByVal arg As T, ByVal arg2 As T) As Integer
End Interface
Interface IComparable(Of In T)
Function CompareTo(ByVal other As T) As Integer
End Interface
若要類型平安,協變類型形參只能以前往類型或只讀屬性方式呈現(例如,可 以是後果類型,好像在 下面的 GetEnumerator 辦法和 Current 屬性中一樣); 逆變類型形參只能以形參或只寫屬性的方式呈現 (例照實參類型,好像在下面的 Compare 和 CompareTo 辦法中一樣)。
協變和逆變是一項風趣的功用,用於泛型接口和委托時可以在一定水平上加強 靈敏性。假如對這些功 能有一些根本理解,在 Visual Studio 2010 中編寫處置 泛型的代碼時會很有協助。