Co- and contra-variance: how do I convert a List(Of Apple) into a List(Of Fruit)?

This is the first in a series of posts exploring how we might implement generic co- and contra-variance in a hypothetical future version of VB. This is not a promise about the next version of VB; it’s just one possible proposal, written up here to get early feedback from potential users.


Sub EatFruit(ByVal x As IEnumerable(Of Fruit))


Dim x As New List(Of Apple)

x.Add(New GrannySmith)

x.Add(New GoldenDelicious)


‘ ERROR: cannot convert List(Of Apple) to IEnumerable(Of Fruit)

Look at the above code. You’d think it should work. It’s a common enough scenario: there’s a library function which handles some kind of data type, but you’ve inherited from that type for your own purposes. How can you pass a collection of your own inherited type into the library function?

We’re considering a VB language feature to support this kind of conversion. The topic is called “Co- and contra-variance”, or just “variance” for short. Variance has actually been in the CLR since 2005 or so, but no one’s yet released a .net language that uses it. There are other languages with it, though. Here are some links to what people have written on the topic.

I’ll talk about how you could use variance practically in VB, where it could make your code easier or cleaner, and what problems it might solve if we implement it. There’s much more to variance than just converting apples into fruit, and it gets trickier as the above articles show, but I think the practical syntax and examples that we’re proposing for VB could demystify it.

Here’s a practical problem I had just yesterday that could have been solved by 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)



Dim call1 = Expression.Call(instance, method, args)

‘ args inherits from IEnumerable(Of ConstantExpression), which

‘ variance-converts to IEnumerable(Of Expression)


For this first article, though, we’ll stick to just fruit.


‘ 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” parameters

We’re thinking of using contextual keywords “Out” and “In” to introduce 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


When the interface declares its type parameter as “Out”, it makes a promise to only ever use that type for function returns (* or other places where it outputs data). The interface will be held to that promise: if it tries to do “Sub f(ByVal x As T)” then it’s a compile-time error. (A lot of the design is constrained by how the CLR uses and represents variance; we want compatibility with other .Net languages.)

It’s this “Out” promise that lets the CLR convert the interface:



‘ 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



In general, if you have a generic interface IReadOnly(Of Out T), then you can cast from it from “Of T” to something that T converts to. And it’s typesafe, for obvious reasons.

Variance conversions are typesafe and efficient. It takes only a single IL instruction to do a variance conversion. There are NO runtime checks required. (This differs from arrays, which have to do a runtime type-check every time you put something into the array.)

Interfaces with “Out” parameters are called covariant in the literature.

“In” parameters

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” parameters are the opposite. When an interface declares one of its type parameter T as “In”, it’s promising only ever to use T for ByVal arguments (* or other places where the interface takes data in). Again the interface will be held to that promise: if it tries to do “Function f() as T” then it’s a compile-time error.

And “In” parameters let you do the opposite kinds of conversion:

‘ 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


Interfaces with “In” parameters are called contravariant in the literature.

“In” and “Out” together

Up until the early 1990s, people used to argue about whether “In” or “Out” parameters were the right thing to have. We now know that they’re both right! The first convincing argument for this was in 1995 in Giuseppe Castagna’s 1995 research paper “Conflict Without A Cause” [PDF].

Here are two examples for why they’re both right, and how they both work together:


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: using “In” and “Out” for internal and external contracts


‘ 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


  End Sub


  Public Function Read() As T Implements IReadOnly(Of T).Read

    Return contents.Pop()

  End Function

End Class


We are eager for customer feedback as we consider whether to add this feature to the VB language, and think about how it might work. Please add your comments.

I’ll be writing more on variance (a lot more) in the weeks to come.

PS. As for the title of this article, here’s what we envisage…

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.




Comments (5)

  1. ljw1004 says:

    [posted by btcheng]

    It seems to me you only need to have one key work Like x as CLONE List(Of T) on function call. Then in the scope of the method x will reference clone of List(Of T). Everything works as normal. Modifications of list are bound into scope of the method or object and will not have effect callers list. Furthermore modifications to objects in the list will persist out of scope which is porsumably is a desired result. This would allow stuff like is.

    Class Fruits

     Public Property Washed() As Boolean

     Public Property Eaten() As Boolean

    End Class

    Class WashedFruitList

     Private mList As List(Of Fruits)

     Public Sub New(ByVal NewList As List(Of Fruits))

     For Each Fruit As Fruits In NewList

     Fruit.Washed = True


     mList = NewList

     End Sub

     Public Sub EatList()

     For Each Fruit As Fruits In mList

     Fruit.Eaten = True


    mList=New List(Of Fruits)

     End Sub

     Public Sub Add(ByVal itm As Fruits)

     itm.Washed = True


     End Sub

    End Class




    Dim lApples As New List (Of Apples)

    Dim CartFriut as WashedFruitList




    CartFriut = New WashedFruitList(lApples)

    ‘ All apples in lApples would be washed


    ‘ lApples would only still have Apples




    ‘ LApples Still Exist Members and all members of LApples would be eaten except NewApple1, and NewApple2




    If you want further clarification email me @

  2. ljw1004 says:

    [posted by Anonymous]

    It seems to me that declaration-site variance (as implemented in the CLR, and now proposed for C# and VB.NET) is less useful than call-site variance (as implemented in Java using wildcards). Here’s why: stop for a moment and think, how many of BCL generic classes could actually be converted to use this feature? There’s IEnumerable<T>, alright – but what else? ICollection<T> is right out, because it is IEnumerable<T> (and hence covariant), but it also takes T as an argument for Add() (which is contravariant) – the intersection of those is invariant. The same goes for all interfaces further extending ICollection<T>, such as IList<T>.

    On the other hand, with wildcards, I can deal with ICollection<T> either in covariant or contravariant fashion: for example, only using GetEnumerator() and Count – a typical real-world scenario – is covariant; only using Add() is contravariant. You can do this with declaration-site variance too, but you have to split every interface into its covariant and contravariant parts, and I doubt the BCL team would do it at this point; besides, it relies upon interface designers to do the right thing, and gives the user no recourse if they don’t.

  3. ljw1004 says:

    Agreed, declaration-site variance does rely on interface designers to do the right thing, and yes I too doubt the BCL team would do it at this point.

  4. Branco Medeiros says:

    This is good stuff!

    (Btw, welcome aboard! It’s a pitty Paul Vicky is moving out, but I guess a fresh new pair of eyes over the language will be a good thing!)

    There were many times I felt the need of covariance (the Out thing), and I knew there was support for it in the CLI. Good to know it’s on your radar. And the syntax seems to fit nicely (too bad parameter constness can’t be enforced in the CLI, that would probably easy things out for covariance, I guess).

    Now there are a few other things the CLI provides that VB could actually have: managed pointers, method Jumping and (ta-dah!) custom array lower bounds =)))

    Congratulations for the very interesting post.


  5. Xenon Headlights says:

    ok nice, the proposal sounds good.