Generics and overriding

Academic experiment of the day. I was interested to see how overriding works with generics. First let’s take a quick look at how overrides generally work.

class BasicOverrider {
  public String DoSomething(Object obj) { return "Object"; }
  public String DoSomething(ICollection collection) { return "ICollection"; }
}
[Test]
public void TestBasicOverriding() {
  BasicOverrider overrider = new BasicOverrider();
  ArrayList someCollection = new ArrayList();
  Assert.That(overrider.DoSomething(5), Is.EqualTo("Object"));
  Assert.That(overrider.DoSomething(someCollection), Is.EqualTo("ICollection"));		
}

This test passes. In general, overriding picks the most specific match for your parameters. In the above example, ICollection is picked even though the ArrayList is also an Object. If we add another override that takes an ArrayList, that will take precedence over the ICollection and Object overrides.

Contrast that to overriding a generic method:

class GenericOverrider {
  public String DoSomething<T>(T someValue) { return "Generic"; }
  public String DoSomething(ICollection someValue) { return "ICollection"; }
}

If I pass in an ArrayList, which overload is called? Here is a passing test:

[Test]
public void TestGenericOverriding() {
  GenericOverrider overrider = new GenericOverrider 
  ArrayList someCollection = new ArrayList();
  ICollection sameCollectionAsICollection = someCollection;
  Assert.That(overrider.DoSomething(5), Is.EqualTo("Generic"));
  Assert.That(overrider.DoSomething(someCollection), Is.EqualTo("Generic"));
  Assert.That(overrider.DoSomething(sameCollectionAsICollection), Is.EqualTo("ICollection"));
  Assert.That(overrider.DoSomething((ICollection) someCollection), Is.EqualTo("ICollection"));
}

The test shows that to call a specific overload of the generic method, your parameters need to match the signature exactly, in this case ICollection, otherwise the generic method will catch it instead. This is due to the single dispatching mechanism in C#.

You can see this binding if you open up the compiled code with IL DASM. The ArrayList binds to the generic method (!!0), whereas the ICollection reference picks up the expected method.

IL_0028:  callvirt   instance string Workshop.UnitTests.GenericOverrider::DoSomething<class [mscorlib]System.Collections.ArrayList>(!!0)
...
IL_003f:  callvirt   instance string Workshop.UnitTests.GenericOverrider::DoSomething(class [mscorlib]System.Collections.ICollection)

Fairly academic I know, but might be of use if you are intending to have specific implementations over a general, generic method.

Comments