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 中編寫處理 泛型的代碼時會很有幫助。