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}")
(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