[原文作者]:Lucian
这是我们如何在VB将来某一个版本中实现Co/Contra-Variance的一系列探索的第一项。 这并不是对下一代VB的承诺,而是作为一个提议放在这里,从而能够从我们潜在的用户那儿得到一些反馈。
Sub EatFruit(ByVal x As IEnumerable(Of Fruit))
...
Dim x As New List(Of Apple)
x.Add(New GrannySmith)
x.Add(New GoldenDelicious)
EatFruit(x)
' ERROR: cannot convert List(Of Apple) to IEnumerable(Of Fruit)
观察一下以上的代码,或许你觉的它是没问题的。这是个很常见的情景:一个库函数处理一些data类型,但是你的自定义类型继承了这个data类型。如何能够将一个自定义类型的集合传给这个库函数呢?
我们正在考虑为VB语言增加一个特征来支持这种转变,我们称之为“Co/Contra-Variance”,简称为“Variance”。实际上大概在2005年的时候,CLR已经支持“Variance”了,但并没有一种发布的语言用到它。但是一些其它的语言用到它,这里有一些链接,都是 关于这个主题的:
- Wikipedia article on co- and contravariance
- Eric Lippert's blog 如何在C#中利用Variance
- [PDF] Contravariance for the rest of us, 来自HP的技术报告
- [PDF] Conflict without a cause, 1995年关于它的一遍核心论文
- Rick ByersCLR中的Variance
- Bruce Eckel's关于Contra-Variance
- Co- and contravariance in java, 通过 "wildcard generics"
我将谈一下VB中如何使用Variance:如何用它让你的代码更简单或清晰,如果我们实现了它能解决什么问题。Variance博大精深,apples转化为fruit只不过是它的寻常一功能而已,以上的文章更是让人觉得它复杂。但是我相信我们提议的语法和例子能够揭去这层神秘的面纱。
昨天我用Variance解决了一个问题:
Function Call(instance As Expression, method As MethodInfo, arguments As IEnumerable(Of Expression)) As MethodCallExpression
...
' Create a new callsite that takes two arguments:
Dim args As New List(Of ConstantExpression)
args.Add(Expression.Constant("x"))
args.Add(Expression.Constant("y"))
'
Dim call1 = Expression.Call(instance, method, args)
' args inherits from IEnumerable(Of ConstantExpression), which
' variance-converts to IEnumerable(Of Expression)
对应于第一段,我们转化为:
' some example classes to get us started
Class Food : End Class
Class Fruit : Inherits Food : End Class
Class Apple : Inherits Fruit : End Class
Class GrannySmith : Inherits Apple : End Class
Class GoldenDelicious : Inherits Apple : End Class
' GoldenDelicious < Apple < Fruit < Food
' using < in the mathematical sense of "is smaller than",
' and in the VB sense of "can be converted to"
Class AppleBasket
Implements IReadOnly(Of Apple)
Implements IWriteOnly(Of Apple)
End Class
“out”参数
我们想用关键字“out”和“in”来介绍Variance:
Interface IReadOnly(Of Out T)
Function Read() As T
End Interface
' "Out" declares that T will only ever be used
' as return type of functions *
Dim x As IReadOnly(Of Apple) = New AppleBasket
Dim y As IReadOnly(Of Fruit) = x
Dim f As Fruit = y.Read()
' This is guaranteed not to throw InvalidCastException
当接口的参数类型是“out”时,它表明这个类型只能用来返回(其他地方表明传出数据),如果试图调用“Sub(ByVal x As T)”,就会产生一个编译错误。(CLR如何使用Variance限制了很多设计,我们希望能和其他的.NET语言兼容。)
正是这个“out”保证了CLR能够转化接口:
' GoldenDelicions < Apple < Fruit < Food < Object
Dim apples As IReadOnly(Of Apple) = New AppleBasket
' It is allowed to change to an IReadOnly of something bigger:
Dim fruits As IReadOnly(Of Fruit) = apples
Dim foods As IReadOnly(Of Food) = apples
Dim things As IReadOnly(Of Object) = fruits
' It is an ERROR to change to an IReadOnly that is smaller:
Dim golds As IReadOnly(Of GoldenDelicious) = apples
' Also an ERROR to change to something unrelated
Dim cars As IReadOnly(Of Car) = apples
通常来说,如果你有一个泛型接口IreadOnly(Of Out T),然后你可以把“Of T”转换为它可以转化的其它类型。很显然,这是类型安全的:
Variance转换是类型安全的和有效的,它只用一句中间语言指令来实现,不需要运行时Runtime检查。(这区别于数组:每次往数组里放东西,都得进行Runtime检查。)
参数类型是“out”的接口被成为covariant。
“In”参数
Interface IWriteOnly(Of In T)
Sub Write(ByVal x As T)
End Interface
' "In" declares that T will only ever be used
' as ByVal arguments to functions.
Dim x As IWriteOnly(Of Apple) = New AppleBasket
Dim z As IWriteOnly(Of GoldenDelicious) = x
z.Write(New GoldenDelicious)
“In”参数正好相反。当接口的参数类型是“In”时,它表明这个类型只能用于ByVal引用(其他地方表明传入数据),如果试图调用“Function f() as T”,就会产生一个编译错误。
“In”参数保证了反向的类型转换:
' GoldenDelcious < Apple < Fruit < Food < Object
Dim apples As IWriteOnly(Of Apple) = New AppleBasket
' It is allowed to convert to an IWriteOnly of something smaller:
Dim golds As IWriteOnly(Of GoldenDelicious) = apples
' It is an ERROR to convert to something bigger, or unrelated:
Dim foods As IWriteOnly(Of Food) = apples
Dim cars As IWriteOnly(Of Car) = apples
参数类型是“out”的接口被成为contravariant。
同时有“In”和“Out”
直到20世纪90年代,人们仍然在为“In”或者“Out”是否是正确的而争论。现在我们知道了他们都是正确的!第一个在这方面有说服力的论据是1995年Giuseppe Castagna的研究论文"Conflict Without A Cause" [PDF]。
这里有两个例子,说明他们为什么是正确的,以及将他们放在一起:
Class AppleBasket
Implements IReadOnly(Of Apple)
Implements IWriteOnly(Of Apple)
Private m_value As Apple
Public Function Read() As Apple Implements IReadOnly(Of Apple).Read
Return m_value
End Function
Public Sub Write(ByVal x As Apple) Implements IWriteOnly(Of Apple).Write
m_value = x
End Sub
End Class
Pipes: 为内部和外部契约(contracts)用“In”和“Out”
' Here we implement a Pipe. Each element in the pipe is an ICollection.
' IList < ICollection < IEnumerable
'
' When we give out reader ("Out") access to the public, we force it so
' readers can only ever assume that elements are IEnumerable.
' And when we give out writer ("In") access, we force it so
' that writers must always put in IList
'
' This future-proofs our code in TWO directions: it forces the
' implementation to provide IList in case in the future we want
' to expose more to the clients; but it does so without making
' a public commitment to the clients that future implementations
' would have to uphold.
Class MyPipe(Of T)
Implements IWriteOnly(Of T)
Implements IReadOnly(Of T)
Private contents As New Stack(Of T)
Public Sub Write(ByVal x As T) Implements IWriteOnly(Of T).Write
contents.Push(x)
End Sub
Public Function Read() As T Implements IReadOnly(Of T).Read
Return contents.Pop()
End Function
End Class
我们很希望能得到用户的反馈,从而帮助我们决定是否将这个功能加入VB语言,并且思考让他如何工作。请踊跃评论。
在以后的几周里,我会新更多关于Variance的东西。
另外:关于这边文正的标题,这是我们的设想:
Dim x As New List(Of Apple)
Dim y As List(Of Fruit) = x
'
' ERROR: List(Of Fruit) cannot be converted to List(Of Apple)
' Consider using IEnumerable(Of Fruit) instead.
打印 | 张贴于 2008-12-17 17:07:39 | Tag:VB Team Blog Visual Basic
留言反馈