Wrox Home  
Search
Professional .NET 2.0 Generics
by Tod Golding
October 2005, Paperback


A Deeper Look

With the conceptual introduction behind us, let's now consider some more detailed examples of generic methods. The following example defines a class, SimpleClass, which declares a series of generic methods that illustrate some of the variations that are available to you when creating your own generic methods. To make things more interesting, all of these generic methods in this example are placed within a generic class. The code for the class is as follows:

[VB code]
Public Class SimpleClass(Of T, U)
    Private _outerVal1 As T
    Private _outerVal2 As U    
	Public Sub New(ByVal val1 As T, ByVal val2 As U)
        Me._outerVal1 = val1
        Me._outerVal2 = val2
    End Sub    
	Public Sub Foo1(Of I)(ByVal innerVal As I)
        Console.Out.WriteLine("Method Param Type  : {0}", 
		innerVal.GetType())
        Console.Out.WriteLine("Class Param Type(T): {0}", 
		_outerVal1.GetType())
    End Sub    
	Public Function Foo2(Of I)(ByVal innerVal As I) As U
        Console.Out.WriteLine("Method Param Type    : {0}", 
		innerVal.GetType())
        Console.Out.WriteLine("Method Return Type(U): {0}", 
		_outerVal1.GetType())
        Return _outerVal2
    End Function    
	Public Sub Foo3(Of T)(ByVal innerVal As T)
        Console.Out.WriteLine("Method Param Type  : {0}", 
		innerVal.GetType())
    End Sub    
	Public Shared Function Foo4(Of I, J)
	(ByVal val1 As I, ByVal val2 As J, _
	ByVal outer As U) As Nullable(Of T)
Dim retVal As New Nullable(Of T)
Console.Out.WriteLine("Static Method Param1 Type : {0}", 
	val1.GetType())
Console.Out.WriteLine("Static Method Param2 Type : {0}", 
	val2.GetType())
Console.Out.WriteLine("Static Method Param3 Type : {0}", 
	outer.GetType())
Console.Out.WriteLine("Static Method Return Type : {0}", 
	retVal.GetType())
Return retVal
    End Function
End Class
[C# code]
public class SimpleClass<T, U> {
    private T _outerVal1;
    private U _outerVal2;    public SimpleClass(T val1, U val2) {
        this._outerVal1 = val1;
        this._outerVal2 = val2;
    }    
	public void Foo1<I>(I innerVal) {
Console.Out.WriteLine("Method Param Type  : {0}", 
	innerVal.GetType());
Console.Out.WriteLine("Class Param Type(T): {0}", _outerVal1.GetType());
    }    public U Foo2<I>(I innerVal) {
Console.Out.WriteLine("Method Param Type    : {0}", 
	innerVal.GetType());
Console.Out.WriteLine("Method Return Type(U): {0}", _outerVal1.GetType());
       return _outerVal2;
    }    
	public void Foo3<T>(T innerVal) {
       Console.Out.WriteLine("Method Param Type  : {0}", 
	   innerVal.GetType());
    }    
	public static Nullable<T> Foo4<I, J>(I val1, J val2, U outer) {
	Nullable<T> retVal = default(T);
	Console.Out.WriteLine("Static Method Param1 Type : {0}", 
		val1.GetType());
	Console.Out.WriteLine("Static Method Param2 Type : {0}", 
		val2.GetType());
	Console.Out.WriteLine("Static Method Param3 Type : {0}", 
		outer.GetType());
	Console.Out.WriteLine("Static Method Return Type : {0}", 
		retVal.GetType());
return retVal;
    }
}

This generic class accepts two type parameters, T and U, and implements four different generic methods. Let's start by looking at the Foo1() method, which accepts a single type parameter I. What's slightly different here is that this method also accesses the T type parameter that belongs to the surrounding class. The goal here is simply to illustrate the accessibility of the surrounding class's type parameters. Every generic method declared in this class may, within any part of its implementation, reference the type parameters that are associated with that class. So, this particular example includes a line of code that writes out the type of the class's T type parameter.

From the perspective of a generic method, type parameters should be treated like any other type that would traditionally be declared within the scope of your class. The rules that govern your method's ability to reference type parameters conform to the same rules that govern the use of non-generic type parameters. The Foo2() method demonstrates one more variation on this theme, referencing the type parameter U as its return type. The idea here is that you shouldn't limit your view of type parameters to just those supplied in the declaration of your method. Leveraging the type parameters of your class and your method in tandem creates a broader view of what can be achieved with a generic method.

The Foo3() method exposes another issue you need to consider when using generic methods within a generic class. The type parameter it accepts shares the same name, T, as a type parameter used by the surrounding class. At a minimum, this creates some confusion. It's not clear what type T will ultimately be assigned. If you run this method in the debugger, however, you'll quickly discover that the T type parameter for Foo3() will always be the type supplied in the call to the method. This, in effect, ends up preventing this method from making any use of the T type parameter that is part of SimpleClass. Clearly, this practice limits the versatility of your generic methods and should be discouraged. Even if your class doesn't need to leverage the type parameters from the class, it would still create some degree of confusion to have these type parameters share the same name. Fortunately, the compiler will generate a warning in this scenario. So, if you happen to do this unintentionally, you'll be notified.

Generic methods may also be static, as demonstrated by the Foo4() method of this example. This method actually illustrates a few separate points. First, it's static and, as such, can be invoked without requiring clients to create an instance of SimpleClass. Even though it's static, it can still reference the type parameters from the class. At first glance, that may seem wrong. However, remember that the type parameters do not reference instance data, they simply define types. This means they can be littered freely throughout your static methods. For this method, U is used as the type of the third parameter and T as part of the return type. Finally, for one last bit of variety, this method also illustrates the use of multiple type parameters.

Calling this static method follows the same conventions as you've seen with calling static methods on generic classes. The only exception is the addition of the new type arguments that accompany the method. Let's look at a small snippet of code that calls the static Foo4() method to see what this might look like:

[VB code]
SimpleClass(Of DateTime, String).Foo4(Of Int32, Double)
	(42, 323.3234, "A Param")
[C# code]
SimpleClass<DateTime, String>.Foo4<int, double>
	(42, 323.3234, "A Param");

You'll notice that this combination of a static generic method within a generic class, both of which accept two type parameters, gets a little unwieldy. Still, it can't be accused of lacking expressiveness or type safety.

Applying Constraints

Anywhere you're allowed to create a generic type, you are also allowed to qualify that type with constraints. For a generic method, the application of constraints to the incoming type parameters is relatively simple. Chapter 7 "Generic Constraints" in the book Professional .NET 2.0 Generics looks at constraints in detail, but here's a quick peek at the mechanics of how constraints are applied to the type parameters of your generic methods:

[VB code]
Public Sub Foo(Of I As IComparable)(ByVal val1 As I)
End Sub
[C# code]
public void Foo<I>(I val1) where I : IComparable {}

As you can see, the syntax here leverages the same approach that you may have seen applied to generic classes. You simply add the interface qualifier for a given type parameter and that will allow you to treat any reference to that the type parameter as conforming to the supplied interface.