C# Generic Method Resolution Gotcha

August 19, 2014

tags: , ,
no comments

Yesterday, I broke some unit tests after removing an unused type parameter from a method, which seemed like a small and harmless code modification. Well, it turns out that it isn’t. If you want to know why, keep reading.

Consider the following code snippet:

   1: [TestFixture]

   2: public class Tests

   3: {

   4:     [Test]

   5:     public void Test()

   6:     {

   7:         Assert.AreEqual("Int", DoSomething(1));

   8:     }

   9:  

  10:     public static string DoSomething<T>(T value)

  11:     {

  12:         return "Generic";

  13:     }

  14:  

  15:     public static string DoSomething(int value)

  16:     {

  17:         return "Int";

  18:     }

  19: }

As expected, the second overload will be invoked and the test will pass. The C# specification specifically dictates that the compiler will always prefer a non-generic method over a generic one.

Now, consider the following modification:

   1: [TestFixture]

   2: public class Tests

   3: {

   4:     [Test]

   5:     public void Test()

   6:     {

   7:         Assert.AreEqual("Int", DoSomething(1));

   8:     }

   9:  

  10:     public static string DoSomething<T>(T value)

  11:     {

  12:         return "Generic";

  13:     }

  14:  

  15:     public static string DoSomething<T>(int value)

  16:     {

  17:         return "Int";

  18:     }

  19: }

All I’ve done is adding a type parameter to the second overload. Note that I’m not even using that type parameter anywhere in the method. What do you think will happen now? Will the test still pass?

The answer is no. This time, the first overload is invoked and the assertion will fail. Why? It seems like the compiler has all it needs in order to choose the second overload.

Notice that when I call the method, I don’t supply the type parameter (e.g DoSomething<int>(1)), I’m leaving it for the compiler to infer. And here lies the problem.

The following paragraph from MSDN explains (emphasis mine):

The compiler can infer the type parameters based on the method arguments you pass in; it cannot infer the type parameters only from a constraint or return value. Therefore type inference does not work with methods that have no parameters. Type inference occurs at compile time before the compiler tries to resolve overloaded method signatures. The compiler applies type inference logic to all generic methods that share the same name. In the overload resolution step, the compiler includes only those generic methods on which type inference succeeded.

There it is, by adding the type parameter to the second method, it is no longer the preferred candidate (it is no longer non-generic).
Because it does not use T as a parameter, the compiler cannot infer the type of T, and is not even considering it as a candidate, which leaves the first overload as the only candidate.

The funny thing is that, by specifying the type parameter (thus effectively taking type-inference out of the equation), the second overload will always be chosen. And because the type parameter is not used by the method, I can specify anything. For example:

   1: Assert.AreEqual("Int", DoSomething<DateTime>(1));

That’s it. Thanks for jon Skeet for pointing me in the right direction.

Cross-posted on http://www.programmingtidbits.com/post/2014/08/19/C-Generic-Method-Resolution-Gotcha.aspx

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*