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.