Generics

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is a complete console program that starts with Option Strict On, was built with warnings treated as errors (unless it is meant to fail, in which case the compiler messages are shown) and was run in the .NET 10 SDK container (Linux); the output shown is real output. The root namespace of the example projects is ShelfDocs, which is why run-time type names such as ShelfDocs.Pair with the CLR arity suffix appear in the output. The project-level imports that the SDK adds (System, System.Collections.Generic, System.Linq, System.Threading.Tasks and others) are why List(Of T) and Func(Of T) need no Imports.

A generic type or procedure is code that is written once and parameterised by one or more types. List(Of Book) is a list that holds only books, Dictionary(Of String, List(Of Book)) maps names to lists of books, and a function Largest(Of T) finds the biggest of anything that can be compared. The compiler checks every use against the type arguments, so a wrong type is a compile-time error instead of an InvalidCastException at run time, and value types are stored without boxing. This page covers how Visual Basic declares generics, how it infers and constrains type arguments, the rules for Nothing, covariance and contravariance with In and Out, what generics look like at run time, the generic collections of the base class library, the generic features that Visual Basic 17.13 can consume from C# (unmanaged, OverloadResolutionPriority) and the ones it cannot, and the compiler errors you will meet. The running example is the generic repository behind the ICatalogStore of the shelf library catalogue.

The shared .NET behaviour (the type system, JIT code generation, the variance annotations of the base class library) is the same as in C#, which is covered in Generics (C#); this page concentrates on the Visual Basic syntax and rules, and on the places where Visual Basic differs.

Why generics exist

Before .NET 2.0 (and Visual Basic 2005, language version 8.0) the only way to write a reusable container was to store Object, as ArrayList and Hashtable still do. Generics replaced that design:

  • Type safety. Dim books As New List(Of Book) refuses books.Add("a string") at compile time. With ArrayList the same mistake compiles and fails later in whatever code casts the element.

  • No casts, no late binding. A List(Of Book) returns Book; the caller never writes DirectCast(list(0), Book).

  • No boxing. A List(Of Integer) stores the integers themselves; an ArrayList allocates a box for each one. The runtime creates specialised code for each value type used as a type argument.

  • One implementation. The algorithm is written once and checked once, for every element type.

The Learn topic on generic types lists the same advantages (type safety, performance, code consolidation, reuse, IDE support). Generics are not a substitute for inheritance: use a generic when the code does not need to know what the type is beyond a few capabilities (comparable, creatable, a reference type), and use a base class or an interface when it needs members.

Terminology

Term Meaning

Generic type

A class, structure, interface or delegate declared with a type parameter list: Class Pair(Of TFirst, TSecond).

Generic procedure

A Sub or Function with its own type parameter list: Function Largest(Of T)(...). A method is not generic just because it sits in a generic class.

Type parameter

The placeholder name in the declaration (T, TKey, TValue). By convention it starts with T.

Type argument

The actual type that replaces it: String in List(Of String).

Constructed type

A generic type with type arguments supplied: List(Of String). It is also called a closed type when no type parameter remains, for example Pair(Of String, Integer).

Open type

A generic type that still has type parameters: Dictionary(Of ,) (the generic type definition), or List(Of T) inside the body of a generic method with type parameter T.

Constraint

A condition on the type argument, introduced by As: T As {Class, IEntity}.

Variance

The rule that lets IEnumerable(Of Book) be used where IEnumerable(Of CatalogItem) is expected, declared with Out and In.

Declaring generic types and procedures

The type parameter list follows the name, in parentheses, and starts with the Of keyword. Of is written once, at the start of the list; further parameters are separated by commas.

Class Name(Of T1, T2 As constraint)
Structure Name(Of T)
Interface Name(Of T)
Delegate Function Name(Of T)(arg As T) As Boolean
Function Name(Of T)(arg As T) As T
  • Classes, structures, interfaces, delegates, Sub and Function can be generic.

  • Modules, enumerations, constructors, properties, events and operators cannot (see the error table below). A generic property is written as a generic class with a property of type T, or as a generic method.

  • A generic procedure may be placed in a non-generic class, structure or module, and a generic class may contain non-generic procedures.

  • Visual Basic is case-insensitive: T and t are the same name (see BC32049 and BC32089 below). Type parameters have the scope of the declaration that introduces them.

  • Visual Basic has no local functions and lambda expressions cannot declare type parameters, so a helper that must be generic is a private Function of the enclosing type or module.

The Learn topic on the Of clause documents the clause itself; the topic on generic procedures documents the rest.

The first example declares a generic class with two type parameters, two generic procedures (one with a constraint), a generic class with a Shared field, and then looks at the constructed types.

Option Strict On

Public Class Pair(Of TFirst, TSecond)
    Public ReadOnly Property First As TFirst
    Public ReadOnly Property Second As TSecond

    Public Sub New(first As TFirst, second As TSecond)
        Me.First = first
        Me.Second = second
    End Sub

    Public Function Swap() As Pair(Of TSecond, TFirst)
        Return New Pair(Of TSecond, TFirst)(Second, First)
    End Function

    Public Overrides Function ToString() As String
        Return "(" & First.ToString() & ", " & Second.ToString() & ")"
    End Function
End Class

Public Class Counter(Of T)
    Public Shared Count As Integer

    Public Sub New()
        Count += 1
    End Sub
End Class

Module Program
    Function Largest(Of T As IComparable(Of T))(first As T, ParamArray rest As T()) As T
        Dim best = first
        For Each candidate In rest
            If candidate.CompareTo(best) > 0 Then best = candidate
        Next
        Return best
    End Function

    Sub SwapValues(Of T)(ByRef left As T, ByRef right As T)
        Dim temp = left
        left = right
        right = temp
    End Sub

    Sub Main()
        Dim entry As New Pair(Of String, Integer)("Dune", 1965)
        Console.WriteLine(entry.ToString() & " -> " & entry.Swap().ToString())

        ' Type arguments inferred from the arguments, or given explicitly.
        Console.WriteLine(Largest(3, 9, 4))
        Console.WriteLine(Largest("pear", "apple", "zebra"))
        Console.WriteLine(Largest(Of Double)(1, 2.5))
        Console.WriteLine(Largest(1, 2.5))
        Console.WriteLine(TypeName(Largest(1, 2.5)))

        Dim a = "left"
        Dim b = "right"
        SwapValues(a, b)
        Console.WriteLine(a & " " & b)

        ' Each closed type has its own Shared fields.
        Dim c1 As New Counter(Of Integer)
        Dim c2 As New Counter(Of Integer)
        Dim c3 As New Counter(Of String)
        Console.WriteLine("Counter(Of Integer): " & Counter(Of Integer).Count)
        Console.WriteLine("Counter(Of String): " & Counter(Of String).Count)

        ' Open and closed constructed types at run time.
        Console.WriteLine(GetType(Pair(Of ,)))
        Console.WriteLine(GetType(Pair(Of String, Integer)))
        Console.WriteLine(GetType(Pair(Of ,)).IsGenericTypeDefinition)
        Console.WriteLine(GetType(Pair(Of String, Integer)).GetGenericTypeDefinition() Is GetType(Pair(Of ,)))
        Console.WriteLine(GetType(List(Of Integer)) Is GetType(List(Of Long)))
        GC.KeepAlive(New Object() {c1, c2, c3})
    End Sub
End Module
(Dune, 1965) -> (1965, Dune)
9
zebra
2.5
2.5
Double
right left
Counter(Of Integer): 2
Counter(Of String): 1
ShelfDocs.Pair`2[TFirst,TSecond]
ShelfDocs.Pair`2[System.String,System.Int32]
True
True
False

Source: Of Clause

What this output shows:

  • Largest(3, 9, 4) is the first inference. T is deduced from the arguments, with no Of Integer anywhere. The constraint IComparable(Of T) is what makes candidate.CompareTo(best) legal.

  • Largest(1, 2.5) mixes an Integer and a Double. Visual Basic infers the dominant type of the arguments, Double, and TypeName confirms it. The rules are in the next section.

  • Counter(Of Integer).Count is 2 and Counter(Of String).Count is 1: every constructed type has its own Shared members. This is the same as in C#, and it surprises programmers who expect one counter per generic class.

  • GetType(Pair(Of ,)) is the open generic type definition (note the empty slot before the comma). The closed type Pair(Of String, Integer) knows its definition, and List(Of Integer) and List(Of Long) are different types. The CLR name of a generic type ends with a backtick and the number of type parameters (the arity), so Pair with two type parameters is printed as ShelfDocs.Pair followed by a backtick and 2.

  • ByRef parameters work as usual in generic procedures; SwapValues(Of T) swaps two strings and would swap two integers just as well.

Type inference

A call to a generic procedure may omit the type arguments. The compiler then infers them from the arguments of the call.

  • Inference uses the arguments only, never the expected result type: Dim e = Echo() for Function Echo(Of T)() As T fails with BC32050 whatever variable receives the result.

  • It is all or nothing. You may omit every type argument or supply them all; you cannot supply some and have the compiler infer the rest.

  • If the arguments suggest several types, the compiler uses the dominant type, which is the one every candidate converts to widening. Largest(1, 2.5) infers Double. When no single type works (an Integer and a String) the call fails with BC36651 and you must name the type yourself, for example Pick(Of Object)(1, "x").

  • Inference exists only for procedures. A constructor call, New Pair(Of String, Integer)("Dune", 1965), always spells out the type arguments, and a missing or extra one is BC32042 or BC32043. Lambda parameters are inferred from the delegate they are converted to, as in books.Select(Function(b) b.Title).

  • Array rank mismatches also defeat inference; the Learn topic on generic procedures gives an example.

  • Option Infer controls the type inference of local variables (Dim e = ...); it does not control the inference of type arguments, which also works with Option Infer Off (tested). See Option Statements.

Constraints

A type parameter with no constraint can be used only through the members of Object, and can be assigned Nothing. Anything more has to be requested with an As clause. Several constraints are written in braces, and As appears once:

Class Repository(Of T As {Class, IEntity})
Function Create(Of T As {CatalogItem, New})(title As String) As T
Constraint What it guarantees and allows C# spelling

T As Class

T is a reference type. Nothing and Is Nothing are meaningful, and T can be used in a type with a reference-type-only restriction.

where T : class

T As Structure

T is a non-nullable value type. New T() is allowed (a Structure constraint implies New), T? is legal, Is Nothing is not.

where T : struct

T As New

T has an accessible parameterless constructor, so New T() compiles. It can be listed in any position ({New, Class} and {Class, New} both compile).

where T : new()

T As BaseClass

T is BaseClass or derives from it; the members of the base class are available on T. At most one class constraint is allowed.

where T : BaseClass

T As IInterface

T implements the interface; its members are available. Any number of interfaces may be listed, and the interface may mention T itself: T As IComparable(Of T).

where T : IInterface

TDerived As TBase

TDerived is TBase or derives from it. A constraint may name another type parameter of the same declaration (Function Upcast(Of TDerived As TBase, TBase)).

where TDerived : TBase

T As unmanaged

Cannot be declared in Visual Basic. Visual Basic 17.13 does enforce it when it calls a generic type or method that C# declared with it (see below).

where T : unmanaged

Rules enforced by the compiler (all the messages are real, from the examples below):

  • Class and Structure cannot appear together: BC32104.

  • Structure and New cannot appear together: BC32103, because Structure already implies the constructor.

  • Class cannot be combined with a specific class constraint: BC32107. A second class constraint is BC32047. A constraint on a NotInheritable class is BC32060, because only that class could ever satisfy it.

  • A constraint must be a class, an interface or a type parameter: T As Integer is BC32048. Object and System.ValueType are rejected with BC32061; use Class or Structure instead.

  • Listing the same interface twice is BC32071; constraining a parameter to itself is BC32113.

  • Visual Basic has no spelling for notnull, default, allows ref struct or nullable-annotated constraints: the constraint list of the Learn Type List topic is interfaces, at most one class, New, and Class or Structure.

The next program uses most of them on the catalogue classes. Everything about T that is not in its constraints is unavailable: Oldest can read item.Year because CatalogItem has the property, and FirstOrNothing can return Nothing because of Class.

Option Strict On

Public Interface IEntity
    ReadOnly Property Id As Integer
End Interface

Public MustInherit Class CatalogItem
    Implements IEntity

    Public Property Id As Integer Implements IEntity.Id
    Public Property Title As String
    Public Property Year As Integer

    Public Overrides Function ToString() As String
        Return Title & " (" & Year & ")"
    End Function
End Class

Public Class Book
    Inherits CatalogItem

    Public Sub New()
        Title = "Untitled book"
        Year = 2000
    End Sub
End Class

Public Class Magazine
    Inherits CatalogItem

    Public Sub New(title As String)
        Me.Title = title
    End Sub
End Class

Module Program
    ' Base-class constraint plus New: both must hold for the type argument.
    Function Create(Of T As {CatalogItem, New})(title As String) As T
        Dim item As New T()
        item.Title = title
        Return item
    End Function

    ' Base-class constraint: members of CatalogItem are available on T.
    Function Oldest(Of T As CatalogItem)(items As IEnumerable(Of T)) As T
        Dim best As T = Nothing
        For Each item In items
            If best Is Nothing OrElse item.Year < best.Year Then best = item
        Next
        Return best
    End Function

    ' Class constraint: a reference-type argument, so Nothing and Is are available.
    Function FirstOrNothing(Of T As Class)(items As IEnumerable(Of T), predicate As Func(Of T, Boolean)) As T
        For Each item In items
            If predicate(item) Then Return item
        Next
        Return Nothing
    End Function

    ' Structure constraint: a non-nullable value type, so T? is legal.
    Function OrFallback(Of T As Structure)(value As T?, fallback As T) As T
        Return If(value.HasValue, value.Value, fallback)
    End Function

    Function ZeroOf(Of T As Structure)() As T
        Return New T()
    End Function

    ' A constraint may name another type parameter of the same procedure.
    Function Upcast(Of TDerived As TBase, TBase)(item As TDerived) As TBase
        Return item
    End Function

    ' With no constraint, Nothing means "the default value of T".
    Function DefaultOf(Of T)() As T
        Return Nothing
    End Function

    ' Is Nothing compiles for an unconstrained T: False for value types, True for a null reference.
    Function IsNull(Of T)(value As T) As Boolean
        Return value Is Nothing
    End Function

    Function IsDefault(Of T)(value As T) As Boolean
        Return EqualityComparer(Of T).Default.Equals(value, Nothing)
    End Function

    Sub Main()
        Dim book = Create(Of Book)("Dune")
        book.Year = 1965
        Dim old = New Book With {.Title = "Emma", .Year = 1815}
        Console.WriteLine(book.ToString() & " / " & Oldest({book, old}).ToString())
        Console.WriteLine(Oldest(New List(Of CatalogItem) From {New Magazine("Wired") With {.Year = 1993}, book}))
        Console.WriteLine(FirstOrNothing({"alpha", "beta"}, Function(s) s.StartsWith("b", StringComparison.Ordinal)))
        Console.WriteLine(FirstOrNothing({"alpha", "beta"}, Function(s) s.Length > 10) Is Nothing)
        Console.WriteLine(OrFallback(Of Integer)(Nothing, 7) & " " & OrFallback(New Date?(#2026-10-19#), Date.MinValue).Year)
        Console.WriteLine(ZeroOf(Of Integer)() & " " & ZeroOf(Of Date)().Year & " " & ZeroOf(Of Boolean)())

        Dim item As CatalogItem = Upcast(Of Book, CatalogItem)(book)
        Console.WriteLine(item.Title)

        Console.WriteLine("default Integer: " & DefaultOf(Of Integer)())
        Console.WriteLine("default String is Nothing: " & (DefaultOf(Of String)() Is Nothing))
        Console.WriteLine("default Date: " & DefaultOf(Of Date)().Year)
        Console.WriteLine(IsNull(0) & " " & IsNull(Of String)(Nothing) & " " & IsNull(Of Integer?)(Nothing))
        Console.WriteLine(IsDefault(0) & " " & IsDefault(5) & " " & IsDefault(Of String)(Nothing) & " " & IsDefault("x"))
    End Sub
End Module
Dune (1965) / Emma (1815)
Dune (1965)
beta
True
7 2026
0 1 False
Dune
default Integer: 0
default String is Nothing: True
default Date: 1
False True True
True False True False

Source: Type List

The compiler also checks every use. Each line in the next listing names a constraint that the type argument does not meet, or an inference that cannot work; the build is expected to fail, and the messages are the real compiler output (the unused-variable warning, BC42024, is suppressed in this project so that only the errors are listed).

Option Strict On

Public Interface IEntity
    ReadOnly Property Id As Integer
End Interface

Public Class Widget
    Implements IEntity
    Public ReadOnly Property Id As Integer Implements IEntity.Id
End Class

Public Class NoDefault
    Public Sub New(x As Integer)
    End Sub
End Class

Public Class Repo(Of T As {Class, IEntity})
End Class

Public Class Maker(Of T As New)
End Class

Public Class Holder(Of T As Structure)
End Class

Module Program
    Function Echo(Of T)() As T
        Return Nothing
    End Function

    Function Pick(Of T)(a As T, b As T) As T
        Return a
    End Function

    Sub Main()
        Dim a As Repo(Of String)
        Dim b As Repo(Of Integer)
        Dim c As Maker(Of NoDefault)
        Dim d As Holder(Of String)
        Dim e = Echo()
        Dim f = Pick(1, "x")
        Dim h As List(Of Object) = New List(Of String)
        Dim i As Repo = Nothing
        Dim j As Repo(Of Widget, Widget)
    End Sub
End Module
Program.vb(36,26): error BC32044: Type argument 'String' does not inherit from or implement the constraint type 'IEntity'.
Program.vb(37,26): error BC32044: Type argument 'Integer' does not inherit from or implement the constraint type 'IEntity'.
Program.vb(37,26): error BC32106: Type argument 'Integer' does not satisfy the 'Class' constraint for type parameter 'T'.
Program.vb(38,27): error BC32083: Type argument 'NoDefault' must have a public parameterless instance constructor to satisfy the 'New' constraint for type parameter 'T'.
Program.vb(39,28): error BC32105: Type argument 'String' does not satisfy the 'Structure' constraint for type parameter 'T'.
Program.vb(40,17): error BC32050: Type parameter 'T' for 'Public Function Echo(Of T)() As T' cannot be inferred.
Program.vb(41,17): error BC36651: Data type(s) of the type parameter(s) in method 'Public Function Pick(Of T)(a As T, b As T) As T' cannot be inferred from these arguments because more than one type is possible. Specifying the data type(s) explicitly might correct this error.
Program.vb(42,36): error BC36756: 'List(Of String)' cannot be converted to 'List(Of Object)'. Consider using 'IEnumerable(Of Object)' instead.
Program.vb(43,18): error BC32042: Too few type arguments to 'Repo(Of T As {Class, IEntity})'.
Program.vb(44,18): error BC32043: Too many type arguments to 'Repo(Of T As {Class, IEntity})'.

Notice that Repo(Of Integer) produces two errors, one for each constraint that Integer fails.

The constraint declarations themselves are checked too. This project declares one invalid type parameter after another:

Option Strict On

Public Interface IEntity
End Interface

Public Class Animal
End Class

Public Class Plant
End Class

Public NotInheritable Class Sealed1
End Class

Public Class A1(Of T As {Animal, Plant})
End Class

Public Class A2(Of T As {Structure, New})
End Class

Public Class A3(Of T As {Class, New})
End Class

Public Class A4(Of T As {IEntity, IEntity})
End Class

Public Class A5(Of T As Sealed1)
End Class

Public Class A6(Of T As {Animal, IEntity, New})
End Class

Public Class A7(Of T As Integer)
End Class

Public Class A8(Of T As T)
End Class

Public Class A9(Of T, U As T)
End Class

Public Class A10(Of T As Object)
End Class

Public Class A12(Of T As System.ValueType)
End Class

Public Class A13(Of T As {Animal, Class})
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(15,34): error BC32047: Type parameter 'T' can only have one constraint that is a class.
Program.vb(18,37): error BC32103: 'New' constraint and 'Structure' constraint cannot be combined.
Program.vb(24,35): error BC32071: Constraint type 'IEntity' already specified for this type parameter.
Program.vb(27,25): error BC32060: Type constraint cannot be a 'NotInheritable' class.
Program.vb(33,25): error BC32048: Type constraint 'Integer' must be either a class, interface or type parameter.
Program.vb(36,25): error BC32113: Type parameter 'T' cannot be constrained to itself:
Program.vb(42,26): error BC32061: 'Object' cannot be used as a type constraint.
Program.vb(45,26): error BC32061: 'ValueType' cannot be used as a type constraint.
Program.vb(48,35): error BC32107: 'Class' constraint and a specific class type constraint cannot be combined.

Source: Type List

The classes A3 (As {Class, New}), A6 (As {Animal, IEntity, New}) and A9 (U As T) compile; every other declaration is an error. Declaration errors are reported before errors inside method bodies, so an unrelated error in a body appears only after the declarations are fixed.

And these declarations cannot be generic at all:

Option Strict On

Public Interface IEntity
End Interface

Public Class Animal
End Class

Public Class Plant
End Class

Public NotInheritable Class Sealed1
End Class

Public Module Mod1(Of T)
End Module

Public Enum Enum1(Of T)
    A
End Enum

Public Class Host
    Public Sub New(Of T)(value As T)
    End Sub

    Public Property Prop(Of T) As Integer

    Public Shared Operator +(Of T)(a As Host, b As Host) As Host
        Return a
    End Operator
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(15,19): error BC32073: Modules cannot be generic.
Program.vb(18,18): error BC32065: Type parameters cannot be specified on this declaration.
Program.vb(23,19): error BC32065: Type parameters cannot be specified on this declaration.
Program.vb(26,25): error BC32065: Type parameters cannot be specified on this declaration.
Program.vb(28,29): error BC32065: Type parameters cannot be specified on this declaration.

Source: Class Statement

Operators are not available on a type parameter

The Learn topic on generic procedures uses CompareTo instead of = in its example and says why: nothing guarantees that a type argument supports the operator. Visual Basic operators are looked up on the declared type, and a type parameter declares none.

  • To compare for equality, use EqualityComparer(Of T).Default.Equals(a, b) (the constraint-free way, which also handles Nothing and Equals overrides), or constrain T As IEquatable(Of T).

  • To order, constrain T As IComparable(Of T) and call CompareTo, or take a Comparer(Of T) or IComparer(Of T) argument.

  • To add, subtract or multiply, take a Func(Of T, T, T) argument, or use System.Linq.Enumerable.Sum and its overloads when the element type is a built-in numeric type.

  • The generic math interfaces such as INumber(Of T) (see Generics (C#)) can be named in a constraint, but their members are static abstract members and Visual Basic 17.13 cannot call them: T.Zero is BC32098 and total += item is BC30452. The compiler accepts the declaration and rejects the use, as the first two errors of the listing show.

Option Strict On
Imports System.Numerics

Public Class CatalogItem
End Class

Public Class Book
    Inherits CatalogItem
End Class

Module Program
    Function SumAll(Of T As INumber(Of T))(items As IEnumerable(Of T)) As T
        Dim total As T = Nothing
        For Each item In items
            total += item
        Next
        Return total
    End Function

    Function ZeroOf(Of T As INumber(Of T))() As T
        Return T.Zero
    End Function

    Function IsNullStructure(Of T As Structure)(value As T) As Boolean
        Return value Is Nothing
    End Function

    Sub Main()
        Dim numbers As IEnumerable(Of Integer) = New List(Of Integer)
        Dim objects As IEnumerable(Of Object) = numbers
        Dim books As IList(Of Book) = New List(Of Book)
        Dim items As IList(Of CatalogItem) = books
        Dim list As List(Of CatalogItem) = New List(Of Book)
    End Sub
End Module
Program.vb(15,13): error BC30452: Operator '+' is not defined for types 'T' and 'T'.
Program.vb(21,16): error BC32098: Type parameters cannot be used as qualifiers.
Program.vb(25,16): error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types.
Program.vb(30,49): error BC36754: 'IEnumerable(Of Integer)' cannot be converted to 'IEnumerable(Of Object)' because 'Integer' is not derived from 'Object', as required for the 'Out' generic parameter 'T' in 'Interface IEnumerable(Of Out T)'.
Program.vb(32,46): error BC36756: 'IList(Of Book)' cannot be converted to 'IList(Of CatalogItem)'. Consider using 'IEnumerable(Of CatalogItem)' instead.
Program.vb(33,44): error BC36756: 'List(Of Book)' cannot be converted to 'List(Of CatalogItem)'. Consider using 'IEnumerable(Of CatalogItem)' instead.

Source: Generic math

The same listing shows BC30020, the reason value Is Nothing is rejected under a Structure constraint, and the last three errors (BC36754 and BC36756), which belong to the variance section below.

Default values and Nothing

Nothing in Visual Basic means "the default value of the type", not only "no object". For a type parameter it is the same rule applied late: Dim x As T = Nothing is null when T is a reference type, 0 when it is a numeric type, False for Boolean, the zero-initialised structure for any other structure. The constrained Class and Structure forms narrow what Nothing can mean. C# spells the same idea default and default(T); Visual Basic has no such keyword. The rules of Nothing itself (the comparison = Nothing, strings, nullable value types) are on Nothing and Nullable Value Types.

  • Return Nothing from a Function ... As T is always legal and always the default of T.

  • value Is Nothing compiles for an unconstrained T. It is False for every value type (the value is boxed for the test) and True for a null reference; the IsNull function of the constraints example prints False True True for an Integer, a String that is Nothing and an Integer? that is Nothing. With a Structure constraint it is rejected with BC30020, because a value type can never be Nothing. With a Class constraint it is the normal null test.

  • EqualityComparer(Of T).Default.Equals(value, Nothing) answers "is this the default value?" for any T, which the IsDefault function above shows: it is True for 0 and for a Nothing string.

  • T?, that is Nullable(Of T), requires T As Structure. OrFallback(Of T As Structure) in the constraints example takes a T?.

The Dim best As T = Nothing line in Oldest is the usual pattern for "no result yet" in a generic search loop; the test best Is Nothing OrElse item.Year < best.Year is legal because T is constrained to the class CatalogItem.

A generic repository for the shelf

The shelf library has a non-generic contract, ICatalogStore, that Catalog depends on. Behind it, the same bookkeeping (auto-numbered identifiers, lookups, "list everything") is needed for books, magazines and loans. A generic repository captures it once. The design is an IEntity interface (everything with an Id), a generic contract IRepository(Of T As IEntity), a generic implementation Repository(Of T As {Class, IEntity}), and an adapter that presents a Repository(Of CatalogItem) as an ICatalogStore.

classDiagram direction LR class IEntity { <<interface>> +Id : Integer } class IRepository~T~ { <<interface>> +GetAll() IReadOnlyList~T~ +Add(item T) +Find(id Integer) T } class ICatalogStore { <<interface>> +GetAll() IReadOnlyList~CatalogItem~ +Add(item CatalogItem) +Find(id Integer) CatalogItem } class Repository~T~ { T As Class, IEntity -_items : Dictionary~Integer, T~ +Add(item T) +Find(id Integer) T +GetAll() IReadOnlyList~T~ } class BookRepository { +ByTitle(text String) IEnumerable~Book~ } class RepositoryCatalogStore class CatalogItem { <<abstract>> +Id : Integer +Title : String } class Book class Magazine IEntity <|.. CatalogItem CatalogItem <|-- Book CatalogItem <|-- Magazine IRepository~T~ <|.. Repository~T~ Repository~T~ <|-- BookRepository : T = Book ICatalogStore <|.. RepositoryCatalogStore RepositoryCatalogStore o-- IRepository~CatalogItem~ : wraps Repository~T~ ..> IEntity : constraint

The diagram shows the relationships: Repository(Of T) implements the generic IRepository(Of T) and requires its argument to implement IEntity; BookRepository closes the type argument as Book and derives from the closed type Repository(Of Book); RepositoryCatalogStore implements the plain ICatalogStore and delegates to an IRepository(Of CatalogItem). In the shelf solution CatalogItem gets its Id through IEntity; the example below is a standalone program, so it declares the minimal versions of the shelf types it needs.

Option Strict On

Public Interface IEntity
    Property Id As Integer
End Interface

Public MustInherit Class CatalogItem
    Implements IEntity

    Public Property Id As Integer Implements IEntity.Id
    Public Property Title As String
    Public Property Year As Integer

    Protected Sub New(title As String, year As Integer)
        Me.Title = title
        Me.Year = year
    End Sub

    Public MustOverride ReadOnly Property Kind As String

    Public Overridable Function Describe() As String
        Return Kind & ": " & Title & " (" & Year & ")"
    End Function
End Class

Public Class Book
    Inherits CatalogItem

    Public Sub New(title As String, year As Integer)
        MyBase.New(title, year)
    End Sub

    Public Overrides ReadOnly Property Kind As String
        Get
            Return "Book"
        End Get
    End Property
End Class

Public Class Magazine
    Inherits CatalogItem

    Public Sub New(title As String, year As Integer)
        MyBase.New(title, year)
    End Sub

    Public Overrides ReadOnly Property Kind As String
        Get
            Return "Magazine"
        End Get
    End Property
End Class

' The shelf's non-generic contract, as in Shelf.Core.
Public Interface ICatalogStore
    Function GetAll() As IReadOnlyList(Of CatalogItem)
    Sub Add(item As CatalogItem)
    Function Find(id As Integer) As CatalogItem
End Interface

' A reusable generic contract and implementation.
Public Interface IRepository(Of T As IEntity)
    Function GetAll() As IReadOnlyList(Of T)
    Sub Add(item As T)
    Function Find(id As Integer) As T
End Interface

Public Class Repository(Of T As {Class, IEntity})
    Implements IRepository(Of T)

    Private ReadOnly _items As New Dictionary(Of Integer, T)
    Private _nextId As Integer = 1

    Public Overridable Sub Add(item As T) Implements IRepository(Of T).Add
        If item Is Nothing Then Throw New ArgumentNullException(NameOf(item))
        If item.Id = 0 Then
            item.Id = _nextId
            _nextId += 1
        End If
        _items.Add(item.Id, item)
    End Sub

    Public Function Find(id As Integer) As T Implements IRepository(Of T).Find
        Dim result As T = Nothing
        _items.TryGetValue(id, result)
        Return result
    End Function

    Public Function GetAll() As IReadOnlyList(Of T) Implements IRepository(Of T).GetAll
        Return _items.Values.ToList()
    End Function
End Class

' A closed generic base class: Repository(Of Book) is an ordinary type.
Public Class BookRepository
    Inherits Repository(Of Book)

    Public Function ByTitle(text As String) As IEnumerable(Of Book)
        Return GetAll().Where(Function(b) b.Title.Contains(text, StringComparison.OrdinalIgnoreCase))
    End Function
End Class

' Adapter: the non-generic store contract implemented on top of the generic repository.
Public Class RepositoryCatalogStore
    Implements ICatalogStore

    Private ReadOnly _repository As IRepository(Of CatalogItem)

    Public Sub New(repository As IRepository(Of CatalogItem))
        _repository = repository
    End Sub

    Public Function GetAll() As IReadOnlyList(Of CatalogItem) Implements ICatalogStore.GetAll
        Return _repository.GetAll()
    End Function

    Public Sub Add(item As CatalogItem) Implements ICatalogStore.Add
        _repository.Add(item)
    End Sub

    Public Function Find(id As Integer) As CatalogItem Implements ICatalogStore.Find
        Return _repository.Find(id)
    End Function
End Class

Module Program
    Sub Main()
        Dim store As ICatalogStore = New RepositoryCatalogStore(New Repository(Of CatalogItem))
        store.Add(New Book("Dune", 1965))
        store.Add(New Magazine("Wired", 1993))
        For Each item In store.GetAll()
            Console.WriteLine(item.Id & " " & item.Describe())
        Next
        Console.WriteLine(store.Find(2).Title)
        Console.WriteLine(store.Find(99) Is Nothing)

        Dim books As New BookRepository()
        books.Add(New Book("Dune", 1965))
        books.Add(New Book("Dune Messiah", 1969))
        books.Add(New Book("Emma", 1815))
        For Each b In books.ByTitle("dune")
            Console.WriteLine(b.Describe())
        Next

        ' The same class, a different closed type: an interface variable of the generic contract.
        Dim viaInterface As IRepository(Of Book) = books
        Console.WriteLine(viaInterface.GetAll().Count & " " & TypeName(viaInterface))
        Console.WriteLine(GetType(BookRepository).BaseType)
    End Sub
End Module
1 Book: Dune (1965)
2 Magazine: Wired (1993)
Wired
True
Book: Dune (1965)
Book: Dune Messiah (1969)
3 BookRepository
ShelfDocs.Repository`1[ShelfDocs.Book]

Reading the example:

  • IRepository(Of T As IEntity) is covered by a constraint; Repository(Of T As {Class, IEntity}) adds Class because the code uses item Is Nothing and returns Nothing from Find. Without Class, Find would return the default value of a structure when nothing is found, which is indistinguishable from a real result.

  • item.Id = 0 and item.Id = _nextId work through the IEntity constraint, with no cast. This is why IEntity.Id is declared read-write here: the repository assigns the next identifier when the item has none.

  • Implements IRepository(Of T).Add names the closed interface member with the class’s own type parameter. The Implements clause is what links a method to the interface member, so the method may have a different name (as FormatBook does in the collections example).

  • Public Overridable Sub Add lets a derived repository override the behaviour; the Overridable modifier is independent of generics. Interfaces are covered on Interfaces and inheritance on Inheritance and Polymorphism.

  • Inherits Repository(Of Book) derives from a closed generic base class. GetType(BookRepository).BaseType prints the CLR name of that closed type, ShelfDocs.Repository with the arity suffix and [ShelfDocs.Book].

  • Dim viaInterface As IRepository(Of Book) = books converts to the generic interface; TypeName still reports the run-time class, BookRepository.

  • store.Find(99) Is Nothing is the "not found" convention for a Class-constrained result. For a result that must also work for value types, use the Try pattern with a ByRef parameter and a Boolean return, as Dictionary(Of TKey, TValue).TryGetValue (used inside Find) and LoanQueue(Of T).TryDequeue below do.

Members of generic types

Shared members and static constructors

Each closed type is a different type, with its own Shared fields, its own Shared Sub New and its own Shared properties. Code in a generic class cannot assume that a Shared value is shared with other constructions of the same generic type. The run-time example below makes this visible with a static constructor that runs once for each construction.

Nested types

A type nested in a generic type is itself generic, with the type parameters of the outer type: LoanQueue(Of T).Node in the collections example is a different type for each T, and its members can use T without declaring it again. A nested type that should not depend on T has to be moved out of the generic class.

Shadowing and naming

A procedure may not reuse its own type parameter’s name as the name of a parameter or local: Make(Of U)(u As U) is BC32089, because Visual Basic ignores case and u and U are the same name. A class-level type parameter T and a parameter named t are not in conflict, as the first method of the example shows. Redeclaring an enclosing type’s type parameter (Sub Test(Of T) inside Holder(Of T)) is accepted, with the warning BC40048, and the inner T shadows the outer. Two type parameters that differ only in case are BC32049.

Option Strict On

Public Class Box(Of T)
    Public Function Wrap(t As T) As T
        Return t
    End Function

    Public Function Make(Of U)(u As U) As U
        Return u
    End Function

    Public Function Local() As T
        Dim t As T = Nothing
        Return t
    End Function
End Class

Public Class Holder(Of T)
    Public Sub Test(Of T)(value As T)
    End Sub
End Class

Public Class Dup(Of T, t)
End Class

Module Program
    Sub Main()
        Console.WriteLine(New Box(Of Integer)().Wrap(3))
    End Sub
End Module
Program.vb(8,32): error BC32089: 'u' is already declared as a type parameter of this method.
Program.vb(19,24): warning BC40048: Type parameter 'T' has the same name as a type parameter of an enclosing type. Enclosing type's type parameter will be shadowed.
Program.vb(23,24): error BC32049: Type parameter already declared with name 't'.

The same case-insensitivity explains a second trap: a local variable and a method that differ only in case collide. The variance example below declares Dim bookFactory As Func(Of Book) = AddressOf MakeBook rather than naming the local makeBook, because a local named makeBook hides the method MakeBook and the AddressOf operand then fails with BC30577 and BC32000.

Overloading on the number of type parameters

Types that differ only in the number of type parameters are different types: Store, Store(Of T) and Store(Of TKey, TValue) can coexist, as Action and Action(Of T) do in the base class library. Methods with the same name and different numbers of type parameters are separate overloads. When only the generic form exists, the name Repo without type arguments is BC32042, as the error listing above shows.

Option Strict On

' A generic MustOverride method: the override must restate the constraints exactly.
Public MustInherit Class Factory
    Public MustOverride Function Make(Of T As {Class, New})() As T
End Class

Public Class DefaultFactory
    Inherits Factory

    Public Overrides Function Make(Of T As {Class, New})() As T
        Return New T()
    End Function
End Class

' Types and methods that differ only in the number of type parameters are different members.
Public Class Store
    Public Function Describe() As String
        Return "Store"
    End Function
End Class

Public Class Store(Of T)
    Public Function Describe() As String
        Return "Store(Of " & GetType(T).Name & ")"
    End Function
End Class

Public Class Store(Of TKey, TValue)
    Public Function Describe() As String
        Return "Store(Of " & GetType(TKey).Name & ", " & GetType(TValue).Name & ")"
    End Function
End Class

Public Class Reporter
    Public Function Go() As String
        Return "Go()"
    End Function

    Public Function Go(Of T)() As String
        Return "Go(Of " & GetType(T).Name & ")()"
    End Function

    Public Function Go(Of T, U)() As String
        Return "Go(Of " & GetType(T).Name & ", " & GetType(U).Name & ")()"
    End Function
End Class

Public Class Outer(Of T)
    ' Inner is generic over the outer T: Outer(Of Integer).Inner and Outer(Of String).Inner are different types.
    Public Class Inner
        Public Function Describe() As String
            Return "Inner of " & GetType(T).Name
        End Function
    End Class
End Class

Module Program
    Sub Main()
        Dim factory As Factory = New DefaultFactory()
        Dim list = factory.Make(Of List(Of Integer))()
        list.Add(7)
        Console.WriteLine("made a list with " & list.Count & " element")

        Console.WriteLine(New Store().Describe())
        Console.WriteLine(New Store(Of Date)().Describe())
        Console.WriteLine(New Store(Of String, Integer)().Describe())

        Dim reporter As New Reporter()
        Console.WriteLine(reporter.Go() & " " & reporter.Go(Of Integer)() & " " & reporter.Go(Of Integer, String)())

        Console.WriteLine(New Outer(Of Integer).Inner().Describe())
        Console.WriteLine(New Outer(Of String).Inner().Describe())
        Console.WriteLine(GetType(Outer(Of Integer).Inner) Is GetType(Outer(Of String).Inner))
    End Sub
End Module
made a list with 1 element
Store
Store(Of DateTime)
Store(Of String, Int32)
Go() Go(Of Int32)() Go(Of Int32, String)()
Inner of Int32
Inner of String
False

Source: Class Statement

Outer(Of Integer).Inner and Outer(Of String).Inner are different types, as the last line shows: the nested class carries the type parameter of its container.

Inheritance and interfaces

  • A generic class can inherit from a closed or open generic class (Inherits Repository(Of Book), Inherits List(Of T)).

  • A type may implement the same generic interface more than once with different type arguments, as CatalogFormatter does below with IFormatter(Of Book) and IFormatter(Of Magazine); each Implements clause names the closed interface member.

  • A MustOverride method may be generic. Unlike in C#, where an override inherits the constraints of the base method, a Visual Basic override must restate them exactly: dropping New from the override of Make(Of T As {Class, New}) is BC32077, "differ by type parameter constraints". DefaultFactory in the example below does it correctly.

Generic structures, delegates and collections

The next program covers the remaining generic constructs. Result(Of T) is a generic structure that models "a value or an error message"; Validator(Of T) is a user-defined generic delegate; LoanQueue(Of T) is a generic class with a nested class; CollectionExtensions.Validate is a generic extension method (see Extension Methods and Partial Methods); and the Main procedure uses the common generic collections.

Option Strict On
Imports System.Runtime.CompilerServices

Public Class Book
    Public ReadOnly Property Title As String
    Public ReadOnly Property Year As Integer
    Public ReadOnly Property Author As String

    Public Sub New(title As String, year As Integer, author As String)
        Me.Title = title
        Me.Year = year
        Me.Author = author
    End Sub

    Public Overrides Function ToString() As String
        Return Title
    End Function
End Class

Public Class Magazine
    Public ReadOnly Property Title As String

    Public Sub New(title As String)
        Me.Title = title
    End Sub
End Class

' The same generic interface implemented twice, with different type arguments.
Public Interface IFormatter(Of In T)
    Function Format(item As T) As String
End Interface

Public Class CatalogFormatter
    Implements IFormatter(Of Book), IFormatter(Of Magazine)

    Public Function FormatBook(item As Book) As String Implements IFormatter(Of Book).Format
        Return "Book " & item.Title & " by " & item.Author
    End Function

    Public Function FormatMagazine(item As Magazine) As String Implements IFormatter(Of Magazine).Format
        Return "Magazine " & item.Title
    End Function
End Class

' A generic structure: a lightweight "value or error" result for parsing.
Public Structure Result(Of T)
    Public ReadOnly Property IsSuccess As Boolean
    Public ReadOnly Property Value As T
    Public ReadOnly Property [Error] As String

    Private Sub New(isSuccess As Boolean, value As T, [error] As String)
        _IsSuccess = isSuccess
        _Value = value
        _Error = [error]
    End Sub

    Public Shared Function Ok(value As T) As Result(Of T)
        Return New Result(Of T)(True, value, Nothing)
    End Function

    Public Shared Function Fail(message As String) As Result(Of T)
        Return New Result(Of T)(False, Nothing, message)
    End Function
End Structure

' A user-defined generic delegate.
Public Delegate Function Validator(Of T)(item As T) As Boolean

' A generic class with a nested class: Node belongs to each closed LoanQueue(Of T).
Public Class LoanQueue(Of T)
    Private Class Node
        Public ReadOnly Property Value As T
        Public Property [Next] As Node

        Public Sub New(value As T)
            Me.Value = value
        End Sub
    End Class

    Private _head As Node
    Private _tail As Node

    Public Sub Enqueue(value As T)
        Dim node As New Node(value)
        If _tail Is Nothing Then
            _head = node
        Else
            _tail.Next = node
        End If
        _tail = node
    End Sub

    Public Function TryDequeue(ByRef value As T) As Boolean
        If _head Is Nothing Then Return False
        value = _head.Value
        _head = _head.Next
        If _head Is Nothing Then _tail = Nothing
        Return True
    End Function
End Class

Public Module CollectionExtensions
    <Extension>
    Public Function Validate(Of T)(items As IEnumerable(Of T), rule As Validator(Of T)) As List(Of T)
        Return items.Where(Function(i) rule(i)).ToList()
    End Function
End Module

Public Class TitleLengthComparer
    Implements IComparer(Of Book)

    Public Function Compare(x As Book, y As Book) As Integer Implements IComparer(Of Book).Compare
        Return x.Title.Length.CompareTo(y.Title.Length)
    End Function
End Class

Module Program
    Function ParseYear(text As String) As Result(Of Integer)
        Dim year As Integer
        If Integer.TryParse(text, year) AndAlso year > 0 Then Return Result(Of Integer).Ok(year)
        Return Result(Of Integer).Fail("'" & text & "' is not a year")
    End Function

    Sub Main()
        Dim books As New List(Of Book) From {
            New Book("Dune", 1965, "Herbert"),
            New Book("Emma", 1815, "Austen"),
            New Book("Neuromancer", 1984, "Gibson"),
            New Book("Dune Messiah", 1969, "Herbert")}

        ' Dictionary(Of TKey, TValue) with a List(Of T) value, built with a lookup-or-create pattern.
        Dim byAuthor As New Dictionary(Of String, List(Of Book))(StringComparer.OrdinalIgnoreCase)
        For Each b In books
            Dim list As List(Of Book) = Nothing
            If Not byAuthor.TryGetValue(b.Author, list) Then
                list = New List(Of Book)
                byAuthor.Add(b.Author, list)
            End If
            list.Add(b)
        Next
        Console.WriteLine(String.Join(", ", byAuthor("HERBERT")))
        Console.WriteLine(byAuthor.ContainsKey("Tolkien"))

        ' SortedDictionary, HashSet, Stack, Queue and KeyValuePair.
        Dim years As New SortedDictionary(Of Integer, String)
        For Each b In books
            years.Add(b.Year, b.Title)
        Next
        For Each pair As KeyValuePair(Of Integer, String) In years
            Console.WriteLine(pair.Key & " " & pair.Value)
        Next
        Dim authors As New HashSet(Of String)(books.Select(Function(b) b.Author))
        Console.WriteLine(authors.Count & " authors; Herbert known: " & authors.Contains("Herbert"))

        ' Sorting with a generic comparer, and with a lambda wrapped by Comparer(Of T).Create.
        books.Sort(New TitleLengthComparer())
        Console.WriteLine(String.Join(", ", books))
        books.Sort(Comparer(Of Book).Create(Function(x, y) x.Year.CompareTo(y.Year)))
        Console.WriteLine(String.Join(", ", books))

        ' One class, two closed generic interfaces.
        Dim formatter As New CatalogFormatter()
        Console.WriteLine(DirectCast(formatter, IFormatter(Of Book)).Format(books(0)))
        Console.WriteLine(DirectCast(formatter, IFormatter(Of Magazine)).Format(New Magazine("Wired")))

        ' A generic structure and a generic delegate.
        For Each candidate In {"1965", "abc"}
            Dim r = ParseYear(candidate)
            Console.WriteLine(If(r.IsSuccess, "year " & r.Value, r.Error))
        Next
        Dim recent = books.Validate(Function(b) b.Year > 1960)
        Console.WriteLine(recent.Count & " recent books")

        ' Generic class with a nested class.
        Dim queue As New LoanQueue(Of String)
        queue.Enqueue("first")
        queue.Enqueue("second")
        Dim value As String = Nothing
        While queue.TryDequeue(value)
            Console.WriteLine("dequeued " & value)
        End While
    End Sub
End Module
Dune, Dune Messiah
False
1815 Emma
1965 Dune
1969 Dune Messiah
1984 Neuromancer
3 authors; Herbert known: True
Dune, Emma, Neuromancer, Dune Messiah
Emma, Dune, Dune Messiah, Neuromancer
Book Emma by Austen
Magazine Wired
year 1965
'abc' is not a year
3 recent books
dequeued first
dequeued second

Points worth noting:

  • Dictionary values that are lists are built with the lookup-or-create pattern (TryGetValue, then Add). With a StringComparer.OrdinalIgnoreCase argument the lookup byAuthor("HERBERT") finds Herbert.

  • SortedDictionary(Of TKey, TValue) enumerates in key order; HashSet(Of T) is the set that removes duplicates; KeyValuePair(Of TKey, TValue) is a generic structure that you can name as the loop variable type.

  • List(Of T).Sort is not stable: elements that compare equal may be reordered. Comparer(Of Book).Create turns a lambda into an IComparer(Of Book).

  • Result(Of T) has a private constructor and two Shared factory functions; with T set to Integer the structure holds the value with no allocation. The member named Error needs brackets ([Error]), because Error is a keyword.

  • The Validator(Of T) delegate type is called with rule(i). The framework’s Predicate(Of T) and Func(Of T, Boolean) are equivalent for this purpose; a user-defined generic delegate is rarely needed.

  • For a loop variable that shadows an in-scope name, the pitfall is described under pitfalls below.

The generic collections live in System.Collections.Generic; the immutable and concurrent ones are in System.Collections.Immutable and System.Collections.Concurrent. The non-generic ArrayList, Hashtable, Queue, Stack and SortedList in System.Collections still exist for compatibility; new code should use the generic ones. The Visual Basic Collection class from the runtime library is a separate legacy type. All of them are compared on Collections and queried with LINQ query expressions.

Type Use

List(Of T)

Ordered, growable array. Fast index access and append; Sort, BinarySearch, RemoveAll, ConvertAll.

Dictionary(Of TKey, TValue)

Hash table. Keys need good GetHashCode and Equals; enumeration order is not part of the contract.

SortedDictionary(Of TKey, TValue), SortedSet(Of T), SortedList(Of TKey, TValue)

Ordered by key; SortedList is array-backed and cheaper in memory, SortedDictionary faster to insert.

HashSet(Of T)

Unique elements, set operations (UnionWith, IntersectWith, ExceptWith).

Queue(Of T), Stack(Of T)

First-in-first-out and last-in-first-out.

LinkedList(Of T)

Doubly linked list with O(1) insertion at a known node.

KeyValuePair(Of TKey, TValue)

Pair structure used by dictionary enumeration.

IEnumerable(Of T), ICollection(Of T), IList(Of T), IReadOnlyList(Of T), IReadOnlyCollection(Of T)

Interfaces; accept the weakest one a method needs (IEnumerable(Of T) for read-only iteration).

Covariance and contravariance

Generic type arguments are normally invariant: IList(Of Book) is not an IList(Of CatalogItem), even though Book derives from CatalogItem, because a list can be written to and a Magazine would then end up in a list of books. Variance recovers the safe cases. A type parameter marked Out may appear only in output positions (return types) and makes the interface or delegate covariant: IEnumerable(Of Book) converts to IEnumerable(Of CatalogItem). A type parameter marked In appears only in input positions (parameters) and makes it contravariant: IComparer(Of CatalogItem) converts to IComparer(Of Book), because a comparer that can compare any two catalogue items can compare two books.

Visual Basic supports variance annotations on generic interfaces and delegates since Visual Basic 2010 (.NET Framework 4); the Learn topic on covariance and contravariance states the same version. The rules:

  • Out and In are allowed on interfaces and delegates only. A class or structure gets BC36722.

  • An Out parameter cannot be used as a ByVal method parameter type (BC36742), an In parameter cannot be used as a return type (BC36743).

  • Variance applies only to reference types as type arguments. IEnumerable(Of Integer) does not convert to IEnumerable(Of Object) (BC36754), because it would need a boxing conversion.

  • Classes and the usual collections are invariant: List(Of Book) does not convert to List(Of CatalogItem) (BC36756, which suggests IEnumerable(Of CatalogItem)).

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String

    Protected Sub New(title As String)
        Me.Title = title
    End Sub
End Class

Public Class Book
    Inherits CatalogItem

    Public Sub New(title As String)
        MyBase.New(title)
    End Sub
End Class

Public Class Magazine
    Inherits CatalogItem

    Public Sub New(title As String)
        MyBase.New(title)
    End Sub
End Class

' Out: T appears only in output positions, so the interface is covariant.
Public Interface IProducer(Of Out T)
    Function Produce() As T
End Interface

' In: T appears only in input positions, so the interface is contravariant.
Public Interface IConsumer(Of In T)
    Sub Consume(item As T)
End Interface

Public Delegate Function Factory(Of Out T)() As T
Public Delegate Sub Handler(Of In T)(item As T)

Public Class BookProducer
    Implements IProducer(Of Book)

    Public Function Produce() As Book Implements IProducer(Of Book).Produce
        Return New Book("Dune")
    End Function
End Class

Public Class ItemPrinter
    Implements IConsumer(Of CatalogItem)

    Public Sub Consume(item As CatalogItem) Implements IConsumer(Of CatalogItem).Consume
        Console.WriteLine("consumed " & item.Title)
    End Sub
End Class

Public Class TitleComparer
    Implements IComparer(Of CatalogItem)

    Public Function Compare(x As CatalogItem, y As CatalogItem) As Integer Implements IComparer(Of CatalogItem).Compare
        Return String.Compare(x.Title, y.Title, StringComparison.OrdinalIgnoreCase)
    End Function
End Class

Module Program
    Function MakeBook() As Book
        Return New Book("Emma")
    End Function

    Sub Main()
        ' Covariance: a Book producer is a CatalogItem producer.
        Dim producer As IProducer(Of CatalogItem) = New BookProducer()
        Console.WriteLine(producer.Produce().Title)

        ' IEnumerable(Of T) is declared Out T; IReadOnlyList(Of T) too.
        Dim books As New List(Of Book) From {New Book("Emma"), New Book("Dune")}
        Dim items As IEnumerable(Of CatalogItem) = books
        Dim readOnlyItems As IReadOnlyList(Of CatalogItem) = books
        Console.WriteLine(items.Count() & " " & readOnlyItems(1).Title)

        ' Contravariance: a CatalogItem consumer can stand in for a Book consumer.
        Dim consumer As IConsumer(Of Book) = New ItemPrinter()
        consumer.Consume(New Book("Dune"))

        ' IComparer(Of T) is declared In T: sort Books with a comparer written for CatalogItem.
        books.Sort(New TitleComparer())
        Console.WriteLine(String.Join(", ", books.Select(Function(b) b.Title)))

        ' The same rules for delegates, with the Out / In modifiers on a user-defined delegate.
        Dim makeItem As Factory(Of CatalogItem) = AddressOf MakeBook
        Console.WriteLine(makeItem().Title)
        Dim handleBook As Handler(Of Book) = New Handler(Of CatalogItem)(Sub(item) Console.WriteLine("handled " & item.Title))
        handleBook(New Book("Dune"))

        ' Func(Of Out TResult) and Action(Of In T) are variant too.
        Dim bookFactory As Func(Of Book) = AddressOf MakeBook
        Dim makeAny As Func(Of CatalogItem) = bookFactory
        Dim actAny As Action(Of CatalogItem) = Sub(item) Console.WriteLine("action " & item.Title)
        Dim actBook As Action(Of Book) = actAny
        actBook(DirectCast(makeAny(), Book))
    End Sub
End Module
Dune
2 Dune
consumed Dune
Dune, Emma
Emma
handled Dune
action Emma

Delegates: Func(Of Book) converts to Func(Of CatalogItem) and Action(Of CatalogItem) converts to Action(Of Book). A separate feature, delegate relaxation, lets AddressOf MakeBook initialise a Factory(Of CatalogItem) even though MakeBook returns Book; it applies to method groups and lambdas, whereas variance applies to conversions between constructed delegate types.

Which BCL types are variant

The table below is not copied from documentation: it is produced by reflection over the open generic definitions (the GenericParameterAttributes of each parameter), run on .NET 10.

Option Strict On
Imports System.Reflection

Module Program
    Function Variance(definition As Type) As String
        Return String.Join(", ", definition.GetGenericArguments().Select(
            Function(p)
                Dim kind = p.GenericParameterAttributes And GenericParameterAttributes.VarianceMask
                Select Case kind
                    Case GenericParameterAttributes.Covariant
                        Return "Out " & p.Name
                    Case GenericParameterAttributes.Contravariant
                        Return "In " & p.Name
                    Case Else
                        Return "invariant " & p.Name
                End Select
            End Function))
    End Function

    Sub Main()
        Dim definitions As Type() = {
            GetType(IEnumerable(Of )), GetType(IEnumerator(Of )), GetType(IReadOnlyList(Of )),
            GetType(IReadOnlyCollection(Of )), GetType(IReadOnlyDictionary(Of ,)),
            GetType(IList(Of )), GetType(ICollection(Of )), GetType(List(Of )), GetType(Dictionary(Of ,)),
            GetType(IComparer(Of )), GetType(IEqualityComparer(Of )), GetType(IComparable(Of )),
            GetType(IEquatable(Of )), GetType(IObservable(Of )), GetType(IObserver(Of )),
            GetType(Func(Of ,)), GetType(Action(Of )), GetType(Predicate(Of )), GetType(Comparison(Of )),
            GetType(Task(Of )), GetType(Lazy(Of )), GetType(Nullable(Of ))}
        For Each definition In definitions
            Console.WriteLine(definition.Name.PadRight(24) & Variance(definition))
        Next
    End Sub
End Module
IEnumerable`1           Out T
IEnumerator`1           Out T
IReadOnlyList`1         Out T
IReadOnlyCollection`1   Out T
IReadOnlyDictionary`2   invariant TKey, invariant TValue
IList`1                 invariant T
ICollection`1           invariant T
List`1                  invariant T
Dictionary`2            invariant TKey, invariant TValue
IComparer`1             In T
IEqualityComparer`1     In T
IComparable`1           In T
IEquatable`1            invariant T
IObservable`1           Out T
IObserver`1             In T
Func`2                  In T, Out TResult
Action`1                In T
Predicate`1             In T
Comparison`1            In T
Task`1                  invariant TResult
Lazy`1                  invariant T
Nullable`1              invariant T

Several of these are not what intuition suggests: IEquatable(Of T) is invariant, as is Task(Of TResult) and Lazy(Of T), and IReadOnlyDictionary(Of TKey, TValue) is invariant in both parameters. IList(Of T) and ICollection(Of T) are invariant because they have methods that take a T.

Variance errors

Option Strict On

Public Class CatalogItem
End Class

Public Class Book
    Inherits CatalogItem
End Class

Public Interface IProducer(Of Out T)
    Function Produce() As T
    Sub Accept(item As T)
End Interface

Public Interface IConsumer(Of In T)
    Sub Consume(item As T)
    Function Current() As T
End Interface

Public Class Cell(Of Out T)
End Class

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(12,24): error BC36742: Type 'T' cannot be used as a ByVal parameter type because 'T' is an 'Out' type parameter.
Program.vb(17,27): error BC36743: Type 'T' cannot be used as a return type because 'T' is an 'In' type parameter.
Program.vb(20,22): error BC36722: Keywords 'Out' and 'In' can only be used in interface and delegate declarations.

Compare with the conversions that fail in Main of the generic-math listing above:

  • IEnumerable(Of Integer) to IEnumerable(Of Object): BC36754, value types do not vary;

  • IList(Of Book) to IList(Of CatalogItem): BC36756, IList is invariant;

  • List(Of Book) to List(Of CatalogItem): BC36756, classes are invariant.

Array covariance

Arrays are covariant for reference element types, a legacy of the .NET 1.0 type system: Dim objects As Object() = strings compiles, and the unsafe half of the design is checked at run time. Writing a value of the wrong type through the widened reference throws ArrayTypeMismatchException (see the run-time example below). Generic collections do not have this hole, which is one more reason to prefer List(Of T) and IReadOnlyList(Of T).

Generics at run time

NET generics are reified: the closed types exist at run time, with their type arguments available to reflection. This is

different from Java, where the type arguments are erased (see Java Reference).

  • GetType(T) and TypeOf value Is T work inside a generic procedure. DirectCast(value, T) compiles for an unconstrained T; at run time a wrong type is an InvalidCastException.

  • GetType(Dictionary(Of ,)) is the generic type definition. MakeGenericType closes it at run time; Activator.CreateInstance creates an instance; MakeGenericMethod closes a generic method.

  • Every value type used as a type argument gets its own code, specialised and unboxed. Reference types share code.

  • A Shared Sub New runs once per closed type.

Option Strict On
Imports System.Reflection

Public Class Registry(Of T)
    Public Shared ReadOnly Created As String

    ' Runs once for each closed type: Registry(Of Integer), Registry(Of String), ...
    Shared Sub New()
        Created = "static constructor ran for " & GetType(T).Name
        Console.WriteLine(Created)
    End Sub

    Public Function Describe(value As T) As String
        If TypeOf value Is IComparable Then
            Return GetType(T).Name & " is comparable"
        End If
        Return GetType(T).Name & " is not comparable"
    End Function
End Class

Public Class Plain
End Class

Module Program
    Function Cast(Of T)(value As Object) As T
        Return DirectCast(value, T)
    End Function

    Function IsInstance(Of T)(value As Object) As Boolean
        Return TypeOf value Is T
    End Function

    Sub Main()
        ' One static constructor run per closed type, on first use.
        Console.WriteLine(New Registry(Of Integer)().Describe(3))
        Console.WriteLine(New Registry(Of Plain)().Describe(New Plain()))
        Console.WriteLine(New Registry(Of Integer)().Describe(4))

        ' T is a real type at run time: TypeOf ... Is T and GetType(T) work.
        Console.WriteLine(IsInstance(Of Integer)(5) & " " & IsInstance(Of String)(5) & " " & IsInstance(Of IComparable)("x"))
        Console.WriteLine(Cast(Of Integer)(CObj(5)) + 1)
        Try
            Console.WriteLine(Cast(Of String)(CObj(5)))
        Catch ex As InvalidCastException
            Console.WriteLine("InvalidCastException: " & ex.Message)
        End Try

        ' Building and inspecting constructed types by reflection.
        Dim open = GetType(Dictionary(Of ,))
        Dim closed = open.MakeGenericType(GetType(String), GetType(List(Of Integer)))
        Console.WriteLine(closed)
        Console.WriteLine(String.Join(" | ", closed.GetGenericArguments().Select(Function(t) t.Name)))
        Dim instance = Activator.CreateInstance(closed)
        Console.WriteLine(instance.GetType().Name & " created, count " & DirectCast(instance, System.Collections.ICollection).Count)

        ' A generic method closed over a type chosen at run time.
        Dim method = GetType(Program).GetMethod("IsInstance", BindingFlags.Static Or BindingFlags.NonPublic Or BindingFlags.Public)
        Dim closedMethod = method.MakeGenericMethod(GetType(Date))
        Console.WriteLine(closedMethod.Invoke(Nothing, New Object() {#2026-10-19#}))

        ' List(Of Integer) stores the numbers unboxed; ArrayList boxes each one.
        Dim numbers As New List(Of Integer) From {1, 2, 3}
        Dim boxed As New ArrayList() From {1, 2, 3}
        Console.WriteLine(numbers.Sum() & " " & boxed.Cast(Of Integer)().Sum())

        ' Arrays are covariant for reference types; the check happens at run time.
        Dim strings As String() = {"a", "b"}
        Dim objects As Object() = strings
        Try
            objects(0) = 42
        Catch ex As ArrayTypeMismatchException
            Console.WriteLine("ArrayTypeMismatchException: " & ex.Message)
        End Try
    End Sub
End Module
static constructor ran for Int32
Int32 is comparable
static constructor ran for Plain
Plain is not comparable
Int32 is comparable
True False True
6
InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.
System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[System.Int32]]
String | List`1
Dictionary`2 created, count 0
True
6 6
ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.

Reflection over generic types is described further on Attributes, Caller Information and Reflection.

Generic features consumed from C#

Visual Basic 17.13 consumes two generic features that C# added after Visual Basic 16.9: the unmanaged constraint and OverloadResolutionPriorityAttribute. You can call code that uses them; the unmanaged constraint cannot be declared in Visual Basic, and neither feature has syntax of its own. This follows the language strategy of History, Versions and Language Strategy.

The unmanaged constraint

C# can declare where T : unmanaged: T is a value type whose fields, at any depth, are also non-nullable value types (no references). Visual Basic 17.13 recognises the constraint on a method or type that came from C# and enforces it; it cannot declare the constraint (As unmanaged is BC30002, "Type 'unmanaged' is not defined", as the Learn topic on generic types also states). Under LangVersion 16.9 or lower the same call is BC36716.

The test needs two projects, because the generic method is written in C#. The library:

using System.Runtime.CompilerServices;

namespace NativeLib;

public struct Dimensions
{
    public int Width;
    public int Height;
}

public struct Labelled
{
    public string Label;
    public int Value;
}

public static class Native
{
    public static int SizeOfValue<T>() where T : unmanaged => Unsafe.SizeOf<T>();

    public static string Describe<T>(T value) where T : unmanaged => $"{typeof(T).Name} is unmanaged";

    // Two overloads: without the attribute, List<int> would pick the more specific List<int> overload.
    public static string Plain(List<int> items) => "List<int>";

    public static string Plain(IEnumerable<int> items) => "IEnumerable<int>";

    public static string Ranked(List<int> items) => "List<int>";

    [OverloadResolutionPriority(1)]
    public static string Ranked(IEnumerable<int> items) => "IEnumerable<int>, priority 1";
}

The Visual Basic project references the library:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>ShelfDocs</RootNamespace>
    <OptionStrict>On</OptionStrict>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <NuGetAudit>false</NuGetAudit>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="../NativeLib/NativeLib.csproj" />
  </ItemGroup>
</Project>

The Visual Basic program that calls it:

Option Strict On
Imports NativeLib

Module Program
    Sub Main()
        Console.WriteLine(Native.SizeOfValue(Of Integer)())
        Console.WriteLine(Native.SizeOfValue(Of Double)())
        Console.WriteLine(Native.SizeOfValue(Of Dimensions)())
        Console.WriteLine(Native.Describe(42))
        Console.WriteLine(Native.Describe(New Dimensions()))

        Dim numbers As New List(Of Integer) From {1, 2, 3}
        Console.WriteLine(Native.Plain(numbers))
        Console.WriteLine(Native.Ranked(numbers))
    End Sub
End Module
4
8
8
Int32 is unmanaged
Dimensions is unmanaged
List<int>
IEnumerable<int>, priority 1

The same library with arguments that violate the constraint:

Option Strict On
Imports NativeLib

Module Program
    Sub Main()
        Console.WriteLine(Native.SizeOfValue(Of String)())
        Console.WriteLine(Native.Describe(New Labelled()))
        Console.WriteLine(Native.Describe("text"))
    End Sub
End Module
Program.vb(6,34): error BC32105: Type argument 'String' does not satisfy the 'Structure' constraint for type parameter 'T'.
Program.vb(6,34): error BC37332: Type argument 'String' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as type parameter 'T'.
Program.vb(7,34): error BC37332: Type argument 'Labelled' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as type parameter 'T'.
Program.vb(8,34): error BC32105: Type argument 'String' does not satisfy the 'Structure' constraint for type parameter 'T'.
Program.vb(8,34): error BC37332: Type argument 'String' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as type parameter 'T'.

BC32105 is the old Structure complaint and BC37332 is the 17.13 message for the unmanaged constraint. A structure that contains a String field (Labelled) fails only the second. With <LangVersion>16.9</LangVersion> in the project, each call to SizeOfValue and Describe in the first program fails with BC36716: Visual Basic 16.9 does not support recognizing 'unmanaged' constraint.

OverloadResolutionPriority

NET 9 added OverloadResolutionPriorityAttribute, which a library author uses to make the compiler prefer one overload when

several are applicable. Visual Basic 17.13 honours the attribute when it resolves a call to such a library method. In the program above, numbers is a List(Of Integer). Native.Plain has overloads for List(Of Integer) and IEnumerable(Of Integer) and picks the more specific List(Of Integer); Native.Ranked has the same two, but the IEnumerable overload carries OverloadResolutionPriority(1), and it wins. Under LangVersion 16.9, the same Ranked(numbers) call chose the List overload, i.e. the attribute was ignored.

Library authors use this to introduce a new overload (for example one that takes a ReadOnlySpan) without breaking callers or creating ambiguities. As a consumer you do not do anything; just be aware that the overload picked can be the one with the highest priority, not the most specific.

Other C# generic features

C# feature Visual Basic 17.13

Generic math and static abstract interface members

The interface can be named in a constraint, but its static abstract members cannot be called (BC32098, BC30452).

unmanaged constraint

Enforced for C# declarations; cannot be declared.

notnull, default, allows ref struct constraints

No syntax; not available as constraints in Visual Basic declarations.

Nullable reference types (string?)

Not available: Visual Basic has no nullable reference type annotations or analysis.

Differences from C#

Topic Visual Basic C#

Type parameters

Class Box(Of T), Function Max(Of T)(...)

class Box<T>, T Max<T>(...)

Open type

GetType(Dictionary(Of ,))

typeof(Dictionary<,>)

Constraints

T As {Class, New, IEntity} (one As, braces)

where T : class, new(), IEntity (one where per type parameter)

Default value

Nothing

default, default(T)

Variance

Interface IProducer(Of Out T), In

interface IProducer<out T>, in

Inference of constructor type arguments

None: New Pair(Of String, Integer)(...) (inference exists only for procedures)

None for classes either: new Pair<string, int>(...)

Local generic functions

Not available (no local functions, lambdas have no type parameters)

Local functions may be generic

Generic math, static abstract

Constraint accepted, static members cannot be called

Fully supported

unmanaged

Consumed only

Declared and consumed

Case

T and t are the same name

Case-sensitive

new T()

New T() with As New or As Structure

new T() with new() or struct

Null test on T

value Is Nothing (not with Structure)

value == null, value is null

Older material you may meet

  • VB6 and VBA have no generics. The only reusable container was Collection, which stores Variant. Ports to .NET that keep Microsoft.VisualBasic.Collection or ArrayList pay for it in casts and boxing. See Migrating Legacy Visual Basic.

  • Early Visual Basic .NET tutorials (2002 and 2003, before generics) build typed collections by inheriting from CollectionBase and writing one class per element type. Replace those classes with List(Of T) or Collection(Of T) from System.Collections.ObjectModel.

  • Tutorials from Visual Basic 2005 or 2008 have no covariance and contravariance; code that works around IEnumerable(Of Derived) to IEnumerable(Of Base) with Cast(Of T) or a copy no longer needs to.

  • Books and articles that mix the .NET Framework and modern .NET often describe the same constructs as language features of a particular Visual Studio edition; the version table is on History, Versions and Language Strategy.

  • Visual Basic 17.13 is the first version that can consume unmanaged constraints and OverloadResolutionPriorityAttribute; materials written for 16.9 and older show errors for them.

Common errors

Code Message (abridged) Cause and fix

BC32044

Type argument does not inherit from or implement the constraint type

The argument is missing the base class or interface. Pass a type that has it.

BC32106

does not satisfy the Class constraint

A value type was given to T As Class.

BC32105

does not satisfy the Structure constraint

A reference type was given to T As Structure.

BC32083

must have a public parameterless instance constructor

As New but the class has only constructors with parameters.

BC37332

must be a non-nullable value type, along with all fields at any level of nesting

An unmanaged constraint declared in C# is not met, for example a structure with a String field.

BC36716

Visual Basic 16.9 does not support recognizing unmanaged constraint

LangVersion is below 17.13; raise it or remove the call.

BC32050

Type parameter cannot be inferred

No argument mentions T; use Echo(Of String)().

BC36651

Data types cannot be inferred from these arguments because more than one type is possible

Pass the type argument explicitly.

BC32042 / BC32043

Too few / too many type arguments

The count of type arguments does not match the arity.

BC32104

Class and Structure cannot be combined

Use one of them.

BC32103

New and Structure cannot be combined

Structure already implies New.

BC32047

only one constraint that is a class

Keep one class constraint; the rest interfaces.

BC32048 / BC32061

Constraint must be a class, interface or type parameter / Object cannot be a constraint

Use Class or Structure.

BC32073 / BC32065

Modules cannot be generic / type parameters cannot be specified on this declaration

Move the generic part into a class or a method.

BC32049 / BC32089

Type parameter already declared / parameter is already declared as a type parameter of this method

Visual Basic ignores case; rename. BC40048 is the matching warning when a method type parameter shadows one of its class.

BC32077

Override differs by type parameter constraints

Restate the constraints of the base method exactly.

BC30020

The Is operator does not accept operands of type T

T As Structure can never be Nothing; drop the test or use T?.

BC30112 / BC30516

Namespace used as an expression / overload resolution failed

A For Each variable named like an imported member (see pitfalls).

BC36722 / BC36742 / BC36743

Out and In only on interfaces and delegates / wrong position

Move T to the correct position or drop the modifier.

BC36754 / BC36756

Cannot convert generic type (variance)

Variance applies to reference types in interfaces and delegates only. Use IEnumerable(Of T).

BC30452

Operator is not defined for T and T

Use a comparer, an interface or a Func.

BC32098

Type parameters cannot be used as qualifiers

T.Zero on a static abstract member; not supported.

BC30002

Type is not defined

As unmanaged cannot be written in Visual Basic.

Pitfalls

  • Every closed type has its own Shared state and its own Shared Sub New.

  • Do not expect List(Of Derived) to convert to List(Of Base). Expose IEnumerable(Of Base) or IReadOnlyList(Of Base).

  • A For Each loop variable with no As clause whose name is already in scope binds to that name instead of declaring a new variable. The listing below uses text (which names the imported namespace System.Text, because Visual Basic ignores case) and input (which names the Input procedure of the runtime library); the compiler reports BC30112 and BC30516 at the loop header and in the body, while entry works. Choose names that are not imported members, or write For Each entry As Book In books.

Option Strict On

Public Class Book
    Public Title As String
End Class

Module Program
    Sub Main()
        Dim books As New List(Of Book) From {New Book()}
        Dim names = {"a", "b"}

        For Each text In names
            Console.WriteLine(text.Length)
        Next

        For Each input In names
            Console.WriteLine(input.Length)
        Next

        For Each entry In names
            Console.WriteLine(entry.Length)
        Next
    End Sub
End Module
Program.vb(12,18): error BC30112: 'System.Text' is a namespace and cannot be used as an expression.
Program.vb(13,31): error BC30456: 'Length' is not a member of 'System.Text'.
Program.vb(16,18): error BC30516: Overload resolution failed because no accessible 'Input' accepts this number of arguments.
Program.vb(17,31): error BC30516: Overload resolution failed because no accessible 'Input' accepts this number of arguments.
  • Pick(1, "x") is BC36651 under Option Strict On and also under Option Strict Off (tested): no single type is dominant for an Integer and a String, and turning Option Strict off does not change that. Name the type argument, Pick(Of Object)(1, "x"). See Option Statements.

  • A type parameter has the members of Object only. If you need ToString it works; if you need an arithmetic or comparison operator, use a constraint or a delegate.

  • Nothing comparisons: x Is Nothing for a T that is a value type is always False. Use EqualityComparer(Of T).Default for "is it the default value".

  • The Class constraint is not "not nullable": T As Class still allows Nothing.

  • Do not expose Dictionary(Of TKey, TValue) or List(Of T) in a public contract when callers only read; prefer the read-only interfaces.

  • Generic code that boxes (ArrayList, Object parameters, non-generic IComparable) loses the benefit of generics.

  • Variance annotations are a public contract: adding Out to an interface and later adding a method that takes T is a breaking change.