Visual Basic 中的泛型协变和逆变.docx
《Visual Basic 中的泛型协变和逆变.docx》由会员分享,可在线阅读,更多相关《Visual Basic 中的泛型协变和逆变.docx(22页珍藏版)》请在冰豆网上搜索。
VisualBasic中的泛型协变和逆变
VisualBasic2010中的泛型协变和逆变
VisualStudio2010有一项名为泛型协变和逆变的新功能,可以用来处理泛型接口和委托。
在VisualStudio2010和Microsoft.NETFramework4之前的版本中,在子类型化方面,泛型的行为是不变的,因此类型参数不同的泛型类型不能相互转换。
例如,如果尝试将List(OfDerived)传递给接受IEnumerable(OfBase)的方法,将会出现错误。
但VisualStudio2010可以处理类型安全的协变和逆变,类型安全的协变和逆变支持泛型接口和委托类型上的协变和逆变类型参数声明。
在本文中,我将具体讨论这一功能,并介绍如何在应用程序中使用这一功能。
因为按钮是控件,根据基本的面向对象继承原则,您可能认为下面的代码可以正常运行:
Dim btnCollection As IEnumerable(Of Button) = New List(Of Button) From {New Button}
Dim ctrlCollection As IEnumerable(Of Control) = btnCollection
尽管这在VisualStudio2008中是不允许的,并会给出错误“IEnumerable(OfButton)无法转换为IEnumerable(OfControl)”。
但作为面向对象编程人员,我们知道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(OfButton)转换为IList(OfControl),然后向其中插入一个完全不是按钮的控件。
VisualStudio2010可以识别并允许类似第一个示例的代码,但在面向.NETFramework4时,仍可以不允许类似第二个示例的代码。
对于大多数用户,在大部分时间里,程序将以预期的方式运行,不需要进一步深究。
但在本文中,我将进一步说明这些代码如何才能正常运行以及其中的原因。
协变
第一段代码将IEnumerable(OfButton)视为IEnumerable(OfControl),为什么在VisualStudio2010中这是代码安全的?
第二个代码示例将IList(OfButton)视为IList(OfControl),为什么这不是安全的?
第一段代码是安全的,原因在于IEnumerable(OfT)是“输出”接口,也就是说,在IEnumerable(OfControl)中,接口用户只是从列表中读取控件。
第二段代码不是安全的,原因在于,IList(OfT)是“输入输出”接口,因此在IList(OfControl)中,接口用户可以读取控件,也可以写入控件。
VisualStudio2010支持这种代码的新语言功能称为泛型协变。
在.NETFramework4中,Microsoft重新编写了框架的以下代码行:
Interface IEnumerable(Of Out T)
...
End Interface
Interface IList(Of T)
...
End Interface
IEnumerable(OfOutT)中的Out批注指示,如果IEnumerable中有方法使用到T,T只能用在输出位置,如函数返回的输出位置或只读属性类型的输出位置。
这样,用户可以将任意IEnumerable(OfDerived)转换为IEnumerable(OfBase),而不会引发InvalidCastException。
IList没有批注,这是因为IList(OfT)是输入输出接口。
这样,用户不能将IList(OfDerived)转换为IList(OfBase),反之亦然;如前所述,这样会引发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(OfButton)的原因。
通常,可以将IComparer(OfBase)安全地转换为任意IComparer(OfDerived)。
这称为逆变。
这是通过与Out批注相对的In批注实现的。
.NETFramework4也进行了修改以引入In泛型类型参数:
Interface IComparer(Of In T)
...
End Interface
因为IComparer(OfT)In批注的存在,IComparer中用到T的每个方法都只在输入位置使用T,如ByVal参数的输入位置或只写属性类型的输入位置。
这样,用户可以将IComparer(OfBase)转换为任意IComparer(OfDerived),而不会引发InvalidCastException。
下面我们看一个.NET4中的Action委托示例,该委托在T中成为逆变的:
Dim actionControl As Action(Of Control)
Dim actionButton As Action(Of Button) = actionControl
此示例在.NET4中运行正常,因为委托actionButton的用户将始终使用Button参数调用它,而Button参数是控件。
您也可以向自己的泛型接口和委托添加In和Out批注。
但由于公共语言运行时(CLR)限制,不能对类、结构或别的对象使用这些批注。
简言之,只有接口和委托可以是协变或逆变的。
声明/语法
VisualBasic使用两个新的上下文关键字:
引入协变的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上下文关键字只用在接口和委托声明中。
在任何其他泛型参数中使用这两个关键字都将导致编译时错误。
VisualBasic编译器不允许变体接口包含嵌套枚举、类和结构,因为CLR不支持变体类。
不过可以在类中嵌套变体接口。
处理多义性
协变和逆变会在成员查询中造成多义性,因此,应该知道是什么引起多义性以及VisualBasic如何处理多义性。
让我们看看图1中的示例,在该示例中,我们尝试将Comparer转换为IComparer(OfControl),其中Comparer实现IComparer(OfButton)和IComparer(OfCheckBox)。
图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(OfButton)和IComparer(OfCheckBox)都可以变体转换为IComparer(OfControl),所以转换不明确。
因此,VisualBasic编译器根据CLR规则查找不明确转换,如果OptionStrict为On,则不允许在编译时进行这样的不明确转换;如果OptionStrict为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(ofIBase)和IComparer(ofIDerived)泛型接口的危险性。
这里,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(OfIAccountRoot)还是IComparer(OfIAccount)接口由CLR进行选择,CLR总是选择接口列表中第一个赋值兼容的接口。
因此通过图3中的代码,Compare()方法会得到不同的结果,因为CLR为Comparer1类选择IComparer(OfIAccountRoot)接口,为Comparer2类选择IComparer(OfIAccount)接口。
泛型接口约束
编写泛型约束(OfTAsU,U)时,现在除继承外还包含了变化可转换性。
图4演示(ofTAsU,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(O