Generics in Visual Basic

MSDN Documentation - .NET Framework

Introduction to Generics

Generics provide a way to create placeholder types that can be used in classes, interfaces, methods, and delegates. This allows you to write code that can operate on different data types without sacrificing type safety or performance. Generics are a fundamental feature of the .NET Framework, enabling the creation of flexible and reusable components.

Before generics, developers often relied on the Object type for collections, which required casting and could lead to runtime errors if the cast was incorrect. Generics solve this by allowing you to specify the type parameter at compile time, ensuring type safety and eliminating the need for explicit casts.

Why Use Generics?

  • Type Safety: Compilers enforce type constraints, preventing runtime type errors.
  • Performance: Avoids the overhead of boxing and unboxing value types when working with collections.
  • Code Reusability: Write a single generic class or method that can be used with various data types.
  • Clarity: Code becomes more readable and self-documenting by clearly indicating the intended data types.

Generic Classes

A generic class is a class that is defined with one or more type parameters. These type parameters act as placeholders for actual types that will be specified when an instance of the generic class is created.

Consider a simple generic class for a stack:

Public Class Stack(Of T)
    Private _items As New System.Collections.Generic.List(Of T)()

    Public Sub Push(item As T)
        _items.Add(item)
    End Sub

    Public Function Pop() As T
        If _items.Count = 0 Then
            Throw New InvalidOperationException("Stack is empty.")
        End If
        Dim item As T = _items(_items.Count - 1)
        _items.RemoveAt(_items.Count - 1)
        Return item
    End Function

    Public ReadOnly Property Count() As Integer
        Get
            Return _items.Count
        End Get
    End Property
End Class

You can then use this generic class with specific types:

' Using Stack with Integers
Dim intStack As New Stack(Of Integer)()
intStack.Push(10)
intStack.Push(20)
Dim topInt As Integer = intStack.Pop() ' topInt will be 20

' Using Stack with Strings
Dim stringStack As New Stack(Of String)()
stringStack.Push("Hello")
stringStack.Push("World")
Dim topString As String = stringStack.Pop() ' topString will be "World"

Generic Methods

Generic methods are methods that declare one or more type parameters. These methods can be called with type arguments that are inferred by the compiler or explicitly provided.

Here's an example of a generic method to swap two values:

Public Module GenericMethods
    Public Sub Swap(Of T)(ByRef a As T, ByRef b As T)
        Dim temp As T = a
        a = b
        b = temp
    End Sub
End Module

Usage:

Dim num1 As Integer = 5
Dim num2 As Integer = 10
Console.WriteLine($"Before Swap: num1 = {num1}, num2 = {num2}")
Swap(Of Integer)(num1, num2) ' Explicitly specify type
Console.WriteLine($"After Swap: num1 = {num1}, num2 = {num2}")

Dim str1 As String = "Apple"
Dim str2 As String = "Banana"
Console.WriteLine($"Before Swap: str1 = {str1}, str2 = {str2}")
Swap(str1, str2) ' Type inferred by compiler
Console.WriteLine($"After Swap: str1 = {str1}, str2 = {str2}")
Tip: When calling a generic method, the .NET compiler can often infer the type arguments based on the arguments passed to the method, so you may not always need to explicitly specify (Of T).

Generic Interfaces

Generic interfaces define contracts that generic types can implement. The most common example is System.Collections.Generic.IEnumerable(Of T), which is implemented by many generic collection types.

Public Interface IRepository(Of TEntity)
    Function GetById(id As Integer) As TEntity
    Sub Add(entity As TEntity)
    Sub Update(entity As TEntity)
    Sub Delete(id As Integer)
    Function GetAll() As IEnumerable(Of TEntity)
End Interface

A concrete implementation might look like:

Public Class Customer
    Public Property CustomerId As Integer
    Public Property Name As String
End Class

Public Class CustomerRepository
    Implements IRepository(Of Customer)

    Private _customers As New List(Of Customer)()

    Public Function GetById(id As Integer) As Customer Implements IRepository(Of Customer).GetById
        Return _customers.FirstOrDefault(Function(c) c.CustomerId = id)
    End Function

    Public Sub Add(entity As Customer) Implements IRepository(Of Customer).Add
        _customers.Add(entity)
    End Sub

    ' ... other methods ...

    Public Function GetAll() As IEnumerable(Of Customer) Implements IRepository(Of Customer).GetAll
        Return _customers
    End Function
End Class

Generic Delegates

Generic delegates allow you to create delegates that can operate on methods with specific parameter and return types without explicitly defining a delegate type for each combination.

The .NET Framework provides predefined generic delegates like Func(Of TResult), Func(Of T, TResult), Action(Of T), etc.

' Using Func(Of T, TResult)
Dim multiply As Func(Of Integer, Integer, Integer) = Function(x, y) x * y
Dim result As Integer = multiply(5, 3) ' result is 15

' Using Action(Of T)
Dim printString As Action(Of String) = Sub(s) Console.WriteLine(s)
printString("This is an action.")

Constraints on Type Parameters

You can specify constraints on type parameters to restrict the types that can be used. This helps ensure that the operations performed within the generic type are valid.

  • Structure: The type parameter must be a value type.
  • Class: The type parameter must be a reference type.
  • New: The type parameter must have a public parameterless constructor.
  • BaseClassName: The type parameter must be or derive from the specified base class.
  • InterfaceName: The type parameter must implement the specified interface.

Example with constraints:

Public Class DataProcessor(Of T As {Structure, New})
    ' T must be a value type and have a parameterless constructor
    Public Function CreateDefault() As T
        Return New T() ' Valid because of the New constraint
    End Function
End Class