When you define a generic class, struct, or interface, any methods defined in these types can refer to a type parameter specified by the type. A type parameter can be used as a method’s parameter, a method’s return type, or as a local variable defined inside the method. However, the CLR also sup- ports the ability for a method to specify its very own type parameters. And these type parameters can also be used for parameters, return types, or local variables.
Here is a somewhat contrived example of a type that defines a type parameter and a method that
has its very own type parameter.
internal sealed class GenericType<T> { private T m_value;
public GenericType(T value) { m_value = value; }
public TOutput Converter<TOutput>() {
TOutput result = (TOutput) Convert.ChangeType(m_value, typeof(TOutput)); return result;
}
}
In this example, you can see that the GenericType class defines its own type parameter (T), and the Converter method defines its own type parameter (TOutput). This allows a GenericType to be constructed to work with any type. The Converter method can convert the object referred to by
the m_value field to various types depending on what type argument is passed to it when called. The ability to have type parameters and method parameters allows for phenomenal flexibility.
A reasonably good example of a generic method is the Swap method.
private static void Swap<T>(ref T o1, ref T o2) { T temp = o1;
Using generic types with methods that take out and ref parameters can be particularly interest- ing because the variable you pass as an out/ref argument must be the same type as the method’s parameter to avoid a potential type safety exploit. This issue related to out/ref parameters is dis- cussed toward the end of the “Passing Parameters by Reference to a Method” section in Chapter 9, “Parameters.” In fact, the Interlocked class’s Exchange and CompareExchange methods offer generic overloads for precisely this reason.1
1 The where clause will be explained in the “Verifiability and Constraints” section later in this chapter.
public static class Interlocked {
public static T Exchange<T>(ref T location1, T value) where T: class; public static T CompareExchange<T>(
ref T location1, T value, T comparand) where T: class;
}
Generic Methods and Type Inference
For many developers, the C# generic syntax can be confusing with all of its less-than and greater- than signs. To help improve code creation, readability, and maintainability, the C# compiler offers type inference when calling a generic method. Type inference means that the compiler attempts to determine (or infer) the type to use automatically when calling a generic method. Here is some code that demonstrates type inference.
Swap(ref s1, ref s2);// Error, type can't be inferred
}
In this code, notice that the calls to Swap do not specify type arguments in less-than/greater-than signs. In the first call to Swap, the C# compiler was able to infer that n1 and n2 are Int32s, and there- fore, it should call Swap by using an Int32 type argument.
When performing type inference, C# uses the variable’s data type, not the actual type of the object referred to by the variable. So in the second call to Swap, C# sees that s1 is a String and s2 is an Object (even though it happens to refer to a String). Because s1 and s2 are variables of dif- ferent data types, the compiler can’t accurately infer the type to use for Swap’s type argument, and it issues the following message: error CS0411: The type arguments for method 'Program. Swap<T>(ref T, ref T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
A type can define multiple methods with one of its methods taking a specific data type and an- other taking a generic type parameter, as in the following example.
In the first call, the compiler could actually call either the Display method that takes a String or the generic Display method (replacing T with String). However, the C# compiler always prefers a more explicit match over a generic match, and therefore, it generates a call to the non- generic Display method that takes a String. For the second call, the compiler can’t call the
non-generic Display method that takes a String, so it must call the generic Display method. By the way, it is fortunate that the compiler always prefers the more explicit match; if the compiler had preferred the generic method, because the generic Display method calls Display again (but with a String returned by ToString), there would have been infinite recursion.
The third call to Display specifies a generic type argument, String. This tells the compiler not to try to infer type arguments but instead to use the type arguments that I explicitly specified. In this case, the compiler also assumes that I must really want to call the generic Display method, so the generic Display will be called. Internally, the generic Display method will call ToString on the passed-in string, which results in a string that is then passed to the non-generic Display method.