Virtual Developer Workshop: Containerized Development with Docker
In The C++ Programming Language, Bjarne Stroustrop discusses the pseudo-invention of template methods in the C programming language by employing the preprocessor and macros. Stroustrop builds on this clever use of C's preprocessor and macro capability, making templates a full-fledged part of the fledgling C with Classes (now C++). The added advantage is that templates are type checked by the compiler rather than simple text substitution by a preprocessor.
.NET 2.0 supports templates called generics. The basic concept is the same: Define a method or a class with one or more methods, specifying the data type as a replaceable element. By convention, the capital letter T is used. Finally, when you use the method or class, indicate a type for the generic T and the compiler will use that new information to generate a new, unique method or class based on that type. The new element becomes a complete and distinct block of code that benefits from being processed by the compiler.
This article shows how to define generic methods and classes and use the generic classes that ship with .NET 2.0.
Defining Generic Methods
The generics capability of .NET 2.0 can be defined at the granularity of a single method. The key is to separate data type from algorithm and parameterize the data type. Each reference to the method with a distinct data type yields a distinct method. Generic methods also support constraints and overloading. A generic method constraint is a new language feature that adds a type constraint to the generic type; this predicate restricts the data type of the generic parameter. Consequently, methods can be overloaded based on the presence and absence of generic parameters. (An upcoming section reviews a few examples of overloaded generic and non-generic methods.)
The classic use of generics is separating data types from common algorithms, the canonical example being turning sort algorithms into generic sorting algorithms. A straightforward example is parameterizing a Swap method. Listing 1 demonstrates how you can define a generic Swap method that swaps any two reference arguments. It includes a console application that demonstrates how to invoke the generic method:
Listing 1: How to Invoke the Generic Method
Module Module1 Sub Main() Dim I As Integer = 5 Dim J As Integer = 7 Swap(Of Integer)(I, J) Console.WriteLine("I = " & I) Console.WriteLine("J = " & J) Dim S As String = "Paul" Dim R As String = "Lori" Swap(Of String)(S, R) Console.WriteLine("S = " & S) Console.WriteLine("R = " & R) Console.ReadLine() End Sub Public Sub Swap(Of T)(ByRef a As T, ByRef b As T) Dim temp As T temp = a a = b b = temp End Sub End Module
Note: In this instance, you also could use an object data type for the Swap method because all .NET types share a common base type.
The new elements needed to define a generic method are the parentheses, the Of keyword, and an argument representing the generic type, as demonstrated by the (Of T) characters after the method name Swap. Then, everywhere the generic type is used, use the parameterized argument (in this example, T).
Swap is a generic method that has two replaceable arguments and one replaceable local variable. For example, Swap(Of Integer) effectively results in a Swap method with two Integer parameters and a temp variable defined as an Integer.
Adding a Generic Method Constraint
Suppose you want to constrain types in Swap (in Listing 1) to non-nullable structure types. You could add a constraint to Swap to indicate that Swap is applicable only to value types (structures). Listing 2 shows Swap defined as being constrained to structures (or value types). The result is that Swap(Of String) in Listing 1 will no longer work:
Listing 2: Swap with a Constraint Limiting the Swap Method to Value Types
Public Sub Swap(Of T As Structure)(ByRef a As T, ByRef b As T) Dim temp As T temp = a a = b b = temp End Sub
Parameterized types can be limited to structures, classes, base classes, interfaces, and types that have a default constructor (Sub New with no parameters). The As predicate in bold in Listing 2 demonstrates how to constrain the parameterized type.
Generics support defining multiple parameterized types, and each parameterized type can have no or multiple constraints.
Overloading Generic Methods
Methods can be overloaded based on arguments but not return types, and methods can be overloaded by parameterized types too. For example, all of the following can exist in the same scope:
Sub Foo Sub Foo(ByVal s As String) Sub Foo(Of T)( ByVal arg As T) Sub Foo(Of T, U)(ByVal arg1 As T, ByVal arg2 as U)