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


Overriding Generic Methods

Generic methods may also be overridden by descendant classes. In fact, generic methods introduce surprisingly few new facets when it comes when overriding methods. The syntax for overriding generic methods follows the same pattern as non-generic methods where the overriding method must match, precisely, the parameters of the parent method. With a generic method, the only difference is that the parameters can be expressed as type parameters. Here's a simple example:

[VB code]
Public Class Person(Of T, U)
Public Overridable Sub Foo1(Of I)(ByVal val1 As T)
    End Sub    
Public Overridable Sub Foo1(Of I)(ByVal val1 As Int32)
    End Sub    
Public Overridable Sub Foo2(Of I, J)(ByVal val1 As U)
    End Sub    
Public Overridable Sub Foo3(Of I, U)(ByVal val1 As I, ByVal val2 As U)
    End Sub    
Public Overridable Sub Foo4(Of D, E, F)(ByVal val1 As D, ByVal val2 As E)
    End Sub    
Public Overridable Function Foo5(Of I As IComparable)
	(ByVal val1 As I) As I
    End FunctionEnd ClassPublic Class Employee(Of T, U)
    Inherits Person(Of String, U)    
'Error: can't verify this is unique for all permutations
Public Overrides Sub Foo1(Of I)(ByVal val1 As Int32)
    End Sub    
Public Overrides Sub Foo2(Of I, J)(ByVal val1 As U)
    End Sub    
Public Overrides Sub Foo3(Of I, U)(ByVal val1 As I, ByVal val2 As U)
    End Sub    
Public Overrides Sub Foo4(Of A, B, C)(ByVal val1 As A, ByVal val2 As B)
    End Sub
End Class
[C# code]
public class Person<T, U> {
    public virtual void Foo1<I>(T val1) {}
    public virtual void Foo1<I>(int val1) {}
    public virtual void Foo2<I, J>(U val1) {}
    public virtual void Foo3<I, U>(I val1, U val2) {}
    public virtual void Foo4<D, E, F>(D val1, E val2) {}
    public virtual I Foo5<I>(I val1) where I : IComparable {
        return default(I);
    }
}public class Employee<T, U> : Person<string, U> {
    public override void Foo1<I>(int val1) {}
    public override void Foo2<I, J>(U val1) {}
    public override void Foo3<I, U>(I val1, U val2) {}
    public override void Foo4<A, B, C>(A val1, B val2) {}
}

A series of examples are shown in this section, each of which attempts to override a generic method. The goal here is to provide a sampling of permutations so you can have a better feel for what's possible. This example sets things up by declaring a generic class, Person, and creating a descendant generic Employee class that overrides a handful of its parent's virtual, generic methods.

Most of the overrides, at this stage, are just as you would expect. The overriding method simply matches the signature of its parent. You should pay particular attention the role type parameters play in this example. In some instances, the type parameters of the surrounding class are referenced and, in others, the generic methods reference their own type parameters. The Foo2() method, for example, accepts type parameters of I and J and references the U type parameter that is part of the class declaration.

The other method here that offers a slight twist is Foo4(). This method matches the parent's signature but uses entirely different type parameter names. This is only meant to demonstrate that — even in an overriding scenario — the names of the type parameters are still just placeholders. The fact that these names are different in the base class does not prevent you from successfully overriding it with alternate type parameter names.

This first example (and those that follow) demonstrates a few areas where VB and C# diverge in their approach to overriding generic methods. In this first set of examples, C# compiles both of these classes successfully. However, VB throws an error on the Foo1() here. It preemptively determines that there are instances where the type for the T parameter can make overloaded versions of Foo1() that collide.

The next example takes this a little further and adds another class that changes the inheritance scheme. The following generic Customer class also extends the Person class and overrides two of its generic methods:

[VB code]
Public Class Customer(Of T, U)
    Inherits Person(Of T, U)    
	'Error: can't verify this is unique for all permutations
    Public Overrides Sub Foo1(Of I)(ByVal val1 As T)
    End Sub    
	Public Overrides Function Foo5(Of I As IComparable)
		(ByVal val1 As I) As I
    End Function
End Class
[C# code]
public class Customer<T, U> : Person<T, U> {
    public override void Foo1<I>(T val1) {}    
	public override I Foo5<I>(I val1) {
        return default(I);
    }
}

In contrast with the previous example, this class uses the T and U type parameters in its inheritance declaration. By referencing the same type parameter for T in both the base and descendant class, you are able to override the Foo1() method that references the T parameter in the base class. This is only possible because the T in both classes is guaranteed to reference the same type. Of course, Foo1() fails in the VB example again for the same reasons discovered in the previous example.

The other override here, the Foo5() method, demonstrates how constraints factor into the signature of a generic method that's being overridden. Here, you might think that Foo5() would not successfully override the declaration in its parent, because the Person class included a constraint as part of its declaration. For C#, the inclusion of the matching constraint would actually generate a compile-time error here. When constraints are part of the base class in C#, the overriding method always inherits the constraints and cannot alter them. The opposite is true in VB, where the overriding method is required to include the constraint as part of the method's signature. The rationale behind this inconsistency is not clear.

There's one final scenario worth exploring. You'll notice that the Person class actually includes an overloaded method, Foo1(). This method has one version that accepts a T type parameter and the other accepts an integer. Now, consider this example where the T type argument supplied to the parent is an integer:

[VB code]
Public Class Vendor(Of T, U)
    Inherits Person(Of Int32, U)    
	Public Overrides Sub Foo1(Of I)(ByVal val1 As Int32)
    End Sub
End Class
[C# code]
public class Vendor<T, U> : Person<int, U> {
    public override void Foo1<I>(int val1) {}
}

This class would seem to be valid. Its declaration of the Foo1() method certainly matches that of the parent class. The problem here isn't that the method doesn't match — it's that two methods from the Person class both match this signature. This issue is caused by the use of an integer in its inheritance from the Person class. That integer causes that the Foo1(T val1) method to collide with the other Foo1() declaration.

As noted earlier, this is one area where VB and C# vary in their handling of the Foo1() method. VB identifies this error at the point of declaration, whereas C# won't throw the error until a type argument is supplied that creates a situation where the signatures of the overloaded methods collide.