March 2010

Volume 25 Number 03

Basic Instincts - Generic Co- and Contravariance in Visual Basic 2010

By Binyam Kelile | March 2010

Visual Studio 2010 has a new feature called generic co- and contravariance that is available when working with generic interfaces and delegates. In versions that precede Visual Studio 2010 and the Microsoft .NET Framework 4, generics behave invariantly with respect to subtyping, so conversions between generic types with different type arguments aren’t allowed.

For example, if you try passing a List(Of Derived) to a method that accepts an IEnumerable(Of Base), you’ll get an error. But Visual Studio 2010 can handle type-safe co- and contravariance that supports declaration of covariant and contravariant type parameters on generic interfaces and delegate type. In this article I’ll discuss what this feature really is and how you can take advantage of it in your applications.

Because a button is a control, you’d expect this code to work because of basic object-oriented inheritance principles:

Dim btnCollection As IEnumerable(Of Button) = New List(Of Button) From {New Button}
Dim ctrlCollection As IEnumerable(Of Control) = btnCollection

It’s not allowed, though, in Visual Studio 2008, which will give the error “IEnumerable(Of Button) cannot be converted to IEnumerable(Of Control) .” But as object-oriented programmers, we know that a value of type Button can be converted to a control, so as noted earlier, according to basic inheritance principles, the code should be allowed.

Consider the following example:

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)

This would fail with an InvalidCastException because the programmer converted the IList(Of Button) into an IList(Of Control), then inserted a control into it that wasn’t even a button.

Visual Studio 2010 recognizes and allows code like that in the first case, but can still disallow the type of code shown in the second case when targeting the .NET Framework 4. For most users, and the majority of the time, programs will just work in the expected way and there’ll be no need to dig deeper. In this article, though, I will dig deeper to explain how and why the code works.

Covariance

In the first listing, which viewed an IEnumerable(Of Button) as an IEnumerable(Of Control), why was the code safe in Visual Studio 2010, while the second code sample, which viewed an IList(Of Button) as an IList(Of Control), unsafe?

The first is OK because IEnumerable(Of T) is an “out” interface, which means that in IEnumerable(Of Control), users of the interface can only take controls out of the list.

The second is unsafe because IList(Of T) is an “in-and-out” interface, so in IList(Of Control), users of the interface can put in controls as well as take them out.

The new language feature in Visual Studio 2010 that allows this is called generic covariance. In the .NET Framework 4, Microsoft has rewritten the framework along these lines:

Interface IEnumerable(Of Out T)
...
End Interface
Interface IList(Of T)
...
End Interface

The Out annotation in IEnumerable(Of Out T) indicates that if a method in IEnumerable mentions T, it will do so only in an out position, such as that for the return of a function or the type of a read-only property. This allows users to cast any IEnumerable(Of Derived) into an IEnumerable(Of Base) without running into an InvalidCastException.

IList lacks an annotation because IList(Of T) is an in-and-out interface. As a result, users can’t cast IList(Of Derived) into IList(Of Base) or vice versa; doing so could lead to an InvalidCastException, as you saw above.

Contravariance

There’s a mirror of the Out annotation. It’s a bit more subtle, so I’ll start with an example:

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)

Here I’ve created a comparer that can determine whether any two controls are the same, which it does by looking at their names only.

Because the comparer can compare any controls at all, it certainly has the ability to evaluate two controls that happen to be buttons. That’s why you can safely cast it to an IComparer(Of Button). In general, you can safely cast an IComparer(Of Base) into any IComparer(Of Derived). This is called contravariance. It’s done with In annotations and is the exact mirror image of the Out annotations.

The .NET Framework 4 has also been modified to incorporate In generic type parameters:

Interface IComparer(Of In T)
 ...
End Interface

Because of the IComparer(Of T) In annotation, every method in IComparer that mentions T will do so only in an in position such as that of a ByVal argument or the type of a write-only property. Thus users can cast an IComparer(Of Base) into any IComparer(Of Derived) without running into an InvalidCastException.

Let’s consider an example of the Action delegate in .NET 4 where the delegate becomes contravariant in T:

Dim actionControl As Action(Of Control)
Dim actionButton As Action(Of Button) = actionControl

The example works in .NET 4 because the user of the delegate actionButton will always invoke it with Button arguments, which are controls.

You can add In and Out annotations to your own generic interfaces and delegates as well. But because of common language runtime (CLR) limitations, you can’t use these annotations on classes, structures or anything else. In short, only interfaces and delegates can be co- or contravariant.

Declarations/Syntax

Visual Basic uses two new contextual keywords: Out, which introduces covariance, and In, which does the same for contravariance, as illustrated in this example:

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

Why do we need these two contextual keywords or the syntax at all, though? Why don’t we infer variance In/Out automatically? First, it’s useful for programmers to declare their intent. Second, there are places where the compiler can’t infer the best variance annotation automatically.

Let’s consider two interfaces, IReadWriteBase and IReadWrite:

Interface IReadWriteBase(Of U)
  Function ReadWrite() As IReadWrite(Of U)
End Interface
Interface IReadWrite(Of T) : Inherits IReadWriteBase(Of T)
End Interface

If the compiler infers them both to be Out, as below, the code works fine:

Interface IReadWriteBase(Of Out U)
  Function ReadWrite() As IReadWrite(Of U)
End Interface
Interface IReadWrite(Of Out T)
  Inherits IReadWriteBase(Of T)
End Interface

And if the compiler infers both to be In, as shown here, again the code works fine:

Interface IReadWrite(Of In T)
  Inherits IReadWriteBase(Of T)
End Interface
Interface IReadWriteBase(Of In U)
  Function ReadWrite() As IReadWrite(Of U)
End Interface

The compiler can’t know which one to pick—In or Out—so it provides a syntax.

Out/In contextual keywords appear in interface and delegate declarations only. Using the keywords in any other generic parameter declaration will cause a compile-time error. The Visual Basic compiler doesn’t allow a variant interface to contain nested enumerations, classes and structures because the CLR doesn’t support variant classes. You can, however, nest variant interfaces inside a class.

Dealing with Ambiguity

Co- and contravariance introduce ambiguity in member lookup, so you should know what triggers ambiguity and how the Visual Basic compiler handles it.

Let’s consider the example in Figure 1, in which we try to convert Comparer to IComparer(Of Control) where Comparer implements IComparer(Of Button) and IComparer(Of CheckBox).

Figure 1 An Ambiguous Conversion

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

Because both IComparer(Of Button) and IComparer(Of CheckBox) are variant-convertible to IComparer(Of Control), the conversion will be ambiguous. As a result, the Visual Basic compiler looks for ambiguities according to the CLR rules, and if Option Strict is On, disallows such ambiguous conversions at compile time; if Option Strict is Off, the compiler generates a warning.

The conversion in Figure 2 succeeds at runtime and doesn’t generate a compile-time error.

Figure 2 A Conversion that Succeeds at Runtime

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

Figure 3 illustrates the danger of implementing both IComparer(of IBase) and IComparer(of IDerived) generic interfaces. Here, Comparer1 and Comparer2 classes implement the same variant generic interface with different generic type parameters in different order. Even though Comparer1 and Comparer2 are identical, apart from ordering when they implement the interface, the call to the Compare method in those classes gives different results.

Figure 3 Different Results from the Same Method

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

Here’s why different results emerge from the code in Figure 3, even though _comp and _comp2 are identical. The compiler just emits Microsoft Intermediate Language that performs the cast. Thus the choice of whether to get the IComparer(Of IAccountRoot) or IComparer(Of IAccount) interface, given an implementation of the Compare() method that’s different, falls to the CLR, which always picks the first assignment-compatible interface in the list of interfaces. So with the code in Figure 3, the Compare() method gives different results because the CLR chooses the IComparer(Of IAccountRoot) interface for Comparer1 class and the IComparer(Of IAccount) interface for Comparer2 class.

Constraints on a Generic Interface

When you write a generic constraint—(Of T As U, U)—As now encompasses variance-convertibility in addition to inheritance. Figure 4 demonstrates that (of T As U, U) encompasses variance-convertibility.

Figure 4 How a Generic Constraint Encompasses Variance-Convertibility

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

A variant generic parameter can be constrained to a different variant parameter. Consider this:

Interface IEnumerable(Of In Tin, Out Tout As Tin)
End Interface
Interface IEnumerable(Of Out Tout, In Tin As Tout)
End Interface

In this example, an IEnumerator(Of ButtonBase, ButtonBase) can be variance-converted to an IEnumerator(Of Control, Button), and IEnumerable(Of Control, Button) can be converted to IEnumerable(Of ButtonBase, ButtonBase) with the constraints still satisfied. Notionally, it could be further variance-converted to IEnumerable(Of ButtonBase, Control), but this no longer satisfies the constraints, so it isn’t a valid type. Figure 5 represents a first-in, first-out collection of objects where a constraint could be useful.

Figure 5 Where a Constraint Is Useful in a Collection of Objects

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

In Figure 5, if I give _IPipe to you, you can only push Button into the pipe, and you can only read Control from it. Note that you can constrain a variant interface to a value type, given that the interface will never allow a variance conversion. Here’s an example with value type constraint in a generic parameter:

Interface IEnumerable(Of Out Tout As Structure)
End Interface

Constraint to value type might be useless, given that variance conversion is not allowed on a variant interface instantiated with value types. But note that with Tout being a structure, it might be useful to infer the type indirectly through constraints.

Constraints on a Function’s Generic Parameters

Constraints in methods/functions must have In types. Here are two basic ways of thinking about constraints on a function’s generic parameters:

  • In most cases, a generic parameter to a function is basically an input to the function, and all inputs must have In types.
  • A client can variance-convert any Out type into System.Object. If a generic parameter is constrained to some Out type, then effectively, a client can remove that constraint, which isn’t what constraints are about.

Let’s consider Figure 6, which makes clear what would happen without this variance-validity rule on constraints.

Figure 6 What Happens Without a Variance-Validity Rule on Constraints

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

In Figure 6, we’ve stored a Label inside m_data as Button, which is illegal. Therefore, constraints in methods/functions must have In types.

Overload Resolution

Overloading refers to creating multiple functions with the same name that take different argument types. Overload resolution is a compile-time mechanism for selecting the best function from a set of candidates.

Let’s take a look at the following example:

Private Overloads Sub Foo(ByVal arg As Integer)
End Sub
Private Overloads Sub Foo(ByVal arg As String)
End Sub

Foo(2)

What actually happens behind the scenes here? When the compiler sees the call to Foo(2), it has to figure out which Foo you want to invoke. To do so, it uses the following simple algorithm:

  1. Generate a set of all applicable candidates by looking up everything with the name Foo. In our example, there are two candidates to consider.
  2. For each candidate, look at the arguments and remove non-applicable functions. Note that the compiler also performs a little verification and type inference for generics.

With the introduction of variance, a set of predefined conversions is expanded, and as the result, Step 2 will accept more candidate functions 
than there were before. Also, in cases where there used to be two equally specific candidates, the compiler would have picked the unshadowed one, but now the shadowed one may be wider, so the compiler may pick it instead. Figure 7 demonstrates code that could potentially break with the addition of variance into Visual Basic.

Figure 7 Code that Might Break with the Addition of Variance into 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

In Visual Studio 2008, the call to Add would bind to Object, but with Visual Studio 2010’s variance-convertibility, we use IEnumerable(Of Control) instead.

The compiler picks a narrowing candidate only if there’s no other, but with variance-convertibility, if there’s a new widening candidate, the compiler picks it instead. If variance-convertibility makes another new narrowing candidate, the compiler emits an error.

Extension Methods

Extension methods enable you to add methods to existing types without creating a new derived type, recompiling or otherwise modifying the original type. In Visual Studio 2008, extension methods support array covariance, as in the following example:

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

But in Visual Studio 2010, extension methods also dispatch on generic variance. This may be a breaking change, as shown in Figure 8, because you may have more extension candidates than before.

Figure 8 A Breaking Change

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

User-Defined Conversions

Visual Basic allows you to declare conversions on classes and structures so that they can be converted to or from other classes and structures as well as basic types. In Visual Studio 2010, variance-convertibility is already added into user-defined conversion algorithms. Therefore, the scope of every user-defined conversion will increase automatically, which might introduce breaks.

Because Visual Basic and C# don’t allow user-defined conversions on interfaces, we need worry only about delegate types. Consider the conversion in Figure 9, which works in Visual Studio 2008 but causes an error in Visual Studio 2010.

Figure 9 A Conversion that Works in Visual Studio 2008 but Produces an Error in 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

Figure 10 gives another example of a conversion that would cause a compile-time error in Visual Studio 2008 with Option Strict On, but will succeed in Visual Studio 2010 with variance-convertibility.

Figure 10 Visual Studio 2010 Allows a Formerly Illegal Conversion by Using Variance Convertibility

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

Effects of Option Strict Off

Option Strict Off normally allows narrowing conversions to be done implicitly. But whether Option Strict is On or Off, variance-convertibility requires its generic arguments to be related through the CLR’s assignment-compatible widening; it’s not enough for them to be related through narrowing (see Figure 11). Note: We do count T->U as narrowing if there’s a variance conversion U->T, and we count T->U as narrowing if T->U is ambiguous.

Figure 11 With 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

Restrictions on Co-and Contravariance

Here’s a list of restrictions with co- and contravariance:

  1. In/Out contextual keywords may appear in interface declarations or in delegate declarations. Using the keywords in any other generic parameter declaration results in a compile-time error. A variant interface can’t nest a class or structure inside it but can contain nested interfaces and nested delegates, which will then take on variance from the containing type.
  2. Only interfaces and delegates can be co- or contravariant, and only when the type arguments are reference types.
  3. Variance conversion can’t be performed on variant interfaces instantiated with value types.
  4. Enumerations, classes, events and structures are not allowed to go to inside a variant interface. This is because we emit these classes/structures/enumerations as generic, inheriting their containers’ generic parameters, so they end up inheriting their containers’ variance. Variant classes/structures/enumerations are disallowed by the CLI spec.

More-Flexible, Cleaner Code

When working with generics, in some cases you may have known you could have written simpler or cleaner code if co- and contravariance had been supported. Now that these features are implemented in Visual Studio 2010 and the .NET Framework 4, you can make your code much cleaner and more flexible by declaring variance properties on type parameters in generic interfaces and delegates.

To facilitate this, in the .NET Framework 4, IEnumerable is now declared covariant using the Out modifier in its type parameter, and IComparer is declared contravariant using the In modifier. So for IEnumerable(Of T) to be variance-convertible to IEnumerable(Of U), you have to have one of the following conditions:

  • Either T inherits from U or
  • T is variance-convertible to U or
  • T has any other kind of predefined CLR reference conversion

In the Basic Class Library, these interfaces are declared as follows:

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

To be type-safe, covariant type parameters can appear only as return types or read-only properties (for example, they can be result types, as in the GetEnumerator method and Current property above); contravariant type parameters can appear only as parameter or write-only properties (argument types, for instance, as in the Compare and CompareTo methods above).

Co- and contravariance are interesting features that eliminate certain inflexibilities when working with generic interfaces and delegates. Having some basic knowledge about these features can be very helpful when writing code that works with generics in Visual Studio 2010.


Binyam Kelile is a software design engineer in Test with the Microsoft Managed Language Team. During the VS 2008 release, he worked on many of the language features, including LINQ Queries and Lexical Closure. For the upcoming Visual Studio release, he worked on co- and contravariance features. You can reach him at binyamk@microsoft.com.

Thanks to the following technical experts for reviewing this article: Beth Massi, Lucian Wischik