Wrong execution path picked by C# compiler when mixing generic and non-generic method signatures.

Note: this entry has moved.

 I've found what seems like a bug in the C# compiler which makes the execution path of your code unpredictable. The scenario is laid out in the product feedback bug but a brief repro will hopefully suffice to convince you of its severity.

Basically, if you have the following class definition:


public class MyClass
{
  public void Add(object value) { ... }
  public void Add(BaseType value) { ... }
  public T Add<T>(T value) { ... }
}

Assuming DerivedType is a class inheriting from BaseType, the following very strange and highly unexpected behavior will happen:

// The method with the non-generic signature is called as expected.
new MyClass().Add(new object());
           

// The method call below should never compile as the method receiving an object returns **void**
// The method with the generic signature is called ?!?!
object o = new MyClass().Add("hola");
           
// Anything that is not exactly of type object will go the generic method :S:S
           
// The match for the type is *exact* meaning that even
// if I have a method of a matching signature according to
// common polymorphic behavior (passing a derived type),
// the generic version is called instead of the expected one.
// the method below should never compile as the method returns **void**
object o2 = new MyClass().Add(new DerivedType());

The worst case scenario is if the method receiving a BaseType returned a BaseType instead of void, you would never know that what's actually being executed is the generic method instead of the expected method (for the second example, passing a new DerivedType), as the returned type would match, but the execution path will never be what you expected.

I believe this is pretty serious, and should get inmediate attention before it's too late. Please vote for the bug if you agree.

2 Comments

  • Daniel: I'm not sure what the bug is in your example.



    You are merely running into overload resolution picking a &quot;better&quot; method based on it's regular lookup rules.



    When the Add method is looked up, two condidate functions are found:



    Add(object o) and

    Add&lt;T&gt;(T t)



    when determining which applies better to Add(&quot;hola&quot;) we can see that we will either have to apply a regular implicit conversion from string to object (which means that the first method is not an exact match). Or we can *exactly* match you call by inferring T to be string. Which is better?



    Add(object) or

    Add(string)



    when you're calling:



    Add(&quot;foo&quot;)



    clearly the second is.



    So the execution path is picked correctly.



    Contact me through my blog if you have any more questions on this.

  • The point of assigning the result to an object was just to show that the method returning void was not being chosen by the compiler, that's it. Of course it's not going to change any type resolution. But having the compiler automatically fall back to the generic method when I'm not explicitly calling the generic version is strange at least. If you remove the generic method and implement another overload receiving (say) an int, the object parameter version will be called. This is equivalent to saying that a generic method with a given name automatically becomes the default overload picked for any argument that doesn't match the **exact** signature of what you pass in, even if you are NOT using a generic method in the call.



    Activator.CreateInstance uses this same pattern, and I've seen in many other places in the BCL, so I wouldn't say it's design error on my side.



    The last example doesn't have anything to do with the covariant problem (judging from what I read on the pointers you gave). It's not about a member trying to change the return type of a base class method, it's about the C# compiler not picking the right overload upon compilation, when the parameter is of a derived type of the expected parameter, and defaulting to the generic method even when you're not using it in the call.

Comments have been disabled for this content.