C# generic method that receives List<T> doesn't call overloaded method for actual type of T (prefers generic one) [duplicate]

  • A+
Category:Languages

This question already has an answer here:

I have this example C# code:

class Stuff { }  // empty class  void Main() {     var list = new List<Stuff> {         new Stuff(),         new Stuff()     };     Fun(list); }  void Fun<T>(List<T> a) {     Debug.Log("called List<T> Fun");     foreach (T t in a) {         Fun(t);     } }  void Fun(Stuff a) {     Debug.Log("called Stuff Fun"); }  void Fun<T>(T a) {     Debug.Log("called T Fun"); } 

Calling Main() ends up printing:

called List<T> Fun called T Fun called T Fun 

I don't understand why the compiler is able to call Fun<T>(List<T> a) as expected, but then does not know to call Fun(Stuff a), which is more specific than Fun<T>(T a). Does it not know for sure at compile time that T is Stuff in this case? Printing typeof(T) inside Fun<T>(List<T> a) gives "Stuff" as expected, but that is not proof of anything...

Adding a Fun(List<Stuff> a) method works but is undesirable (lots of different possible types for Lists in the project, and the behaviour is supposed to be the same for all of them).

I have tried searching for this problem but couldn't phrase it in a way that I could find it. Sorry if someone asked this before (which is likely!).

 


The key is to understand that void Fun<T>(List<T> a) is compiled once, with overload resolution performed once. Not once per T, but a single time.

Consider the compiler situation when it's compiling this code:

void Fun<T>(List<T> a) {     Debug.Log("called List<T> fun");     foreach (T t in a) {         Fun(t);     } } 

In particular, consider the overload resolution in the call to Fun(t).

The compiler knows nothing about T, which is the type of the argument in the Fun(t) call - it could be any non-pointer type. It has to perform overload resolution between these signatures:

void Fun<T>(List<T> a) void Fun(Stuff a) void Fun<T>(T a) 

The only one of those methods which is applicable is the last - the T in the calling code is used as the type argument for the T in the method we're calling, and it's fine. The other two methods aren't applicable, because there's no conversion from T to List<TList> (for any TList), or from T to Stuff.

If you want the overload resolution to be performed at execution time instead, you could use dynamic typing:

foreach (dynamic d in a) {     Fun(d); } 

I don't personally like doing that, but it might do what you want in this case. On the other hand, with nested lists it could get tricky - if you T is a List<int>, then would you expect it to call Fun<List<int>>(list) or Fun<int>(list)? I honestly can't remember the rules offhand to know which of those is "better" or whether it's ambiguous.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: