13

I have two extension methods on IDataReader with the following signatures:

internal static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)

internal static double? GetDoubleOrNull(this IDataReader reader, string columnName)

GetDoubleOrNull does not have any overloads.

Elsewhere, I'm able to do

Func<string, double?> del = reader.GetDoubleOrNull;

var x = reader.GetList(del);

or

var x = reader.GetList<double?>(reader.GetDoubleOrNull);

or just pass in an instance method like

public double? blah(string s)

var x = reader.GetList(blah);

but I can't do

var x = reader.GetList(reader.GetDoubleOrNull);

The compiler gives the error

cannot convert from 'method group' to 'System.Func<string,double?>'

I don't understand this. I thought that since there's no overload on GetDoubleOrNull, there wouldn't be an overload resolution and it could infer the type parameter from the method signature.

The really confusing part is how it seems to work when passing in blah.

Moss
  • 855
  • 1
  • 9
  • 23
  • 3
    Very interesting. An explicit cast ``var x = reader.GetList((Func)reader.GetDoubleOrNull)`` also works. Resharper marks the cast as redundant, but without it compilation fails. Is Jon Skeet around? – Jacek Gorgoń Mar 07 '12 at 12:46
  • 3
    Is there a way to invoke Jon Skeet (or maybe Eric Lippert)? Say their name three times or something? – Chris Mar 07 '12 at 12:54
  • 1
    related: http://stackoverflow.com/questions/7745852/method-inference-does-not-work-with-method-group and http://stackoverflow.com/questions/2057146/compiler-ambiguous-invocation-error-anonymous-method-and-method-group-with-fun – AakashM Mar 07 '12 at 12:57

2 Answers2

8

Preface: Skip to the edit if you want the complete explanation. Spoiler: Extension Methods are a compiler trick, and actually have one more argument than they look like when you invoke them.

It's in the resolution of the method group. Apparently the C# compiler doesn't take the time to figure out whether the method you are using has overloads or not; it just always requires an explicit cast. Check out:

What is a method group in C#?
Method Inference does not work with method group

The method group that comes back from reader.GetDoubleOrNull is narrowed down by what you try to cast it to: GetDoubleOrNull could refer to any number of overloaded methods with that name. You must explicitly cast it.

Interestingly, you can't even assign a method group to an implicitly-typed variable for the same reason:

var x = reader.GetDoubleOrNull;

fails to compile because it requires an explicit cast.

Edit I'm pretty sure that the confusion here has to do with extension methods:

Check out the following test class:

public static class Extensions
{
    public static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)
    {
        throw new NotImplementedException();
    }

    public static double? GetDoubleOrNull(this IDataReader reader, string columnName)
    {
        throw new NotImplementedException();
    }

    public static double? blah(this string s)
    {
        throw new NotImplementedException();
    }
}

You can successfully call

var x = reader.GetList(Extensions.blah);

Why could this be? blah is a static extension method as well, so, based on your evidence, it would seem like the above line should not compile. Further complicating things, let's add this method:

public static List<T> GetList2<T>(this IDataReader reader, Func<IDataReader, string, T> del) 
{ 
    throw new NotImplementedException(); 
}

You can now call

x = reader.GetList2(Extensions.GetDoubleOrNull);

and it will compile properly. What gives?

Here's the answer: Extension Methods do not actually add methods to your objects. They're really a compiler trick to allow you to program as if those methods were part of your classes. From here:

In your code you invoke the extension method with instance method syntax. However, the intermediate language (IL) generated by the compiler translates your code into a call on the static method. Therefore, the principle of encapsulation is not really being violated. In fact, extension methods cannot access private variables in the type they are extending.

So, when you call

var x = reader.GetDoubleOrNull("myColumnName");

what is actually being compiled and executed is essentially this (a perfectly legitimate call, even though it's an extension method):

var x = Extensions.GetDoubleOrNull(reader, "myColumnName");

So, when you try to use GetDoubleOrNull as an arg for a Func<string, double?>, the compiler is going "I can turn GetDoubleOrNull into a Func<IDataReader, string, double?> because it has two arguments, but I don't know how to turn it into a Func<string, double?>"

Even though you're calling it as if it's an instance method of the IDataReader with one arg, it is not: it's just a static method with two args that the C# compiler has tricked you into thinking is part of IDataReader.

Community
  • 1
  • 1
eouw0o83hf
  • 9,438
  • 5
  • 53
  • 75
  • Excellent answer and research. I want to +1 it again. ;-) – Chris Mar 07 '12 at 14:42
  • 3
    I'm not Jon Skeet, but I do what I can :D – eouw0o83hf Mar 07 '12 at 14:48
  • Thanks for your answer. Unfortunately still I don't seem to understand the issue clearly. The first question you referenced is one that I'm aware of. I read the second one, as well as those posted by AakashM, and it looks to me that it comes down to how overload resolution doesn't take return types into account. I understand that. But how, then, does passing in `blah` work? Is there no overload resolution that takes place in that case? If not, why not? If so, how is it that it succeeds? – Moss Mar 07 '12 at 16:26
  • Doesn't need to resolve it you told it what to do with public double? blah(string s); – Tony Hopkinson Mar 07 '12 at 23:25
  • @Moss: I updated the answer to explain with a little more clarity. – eouw0o83hf Mar 08 '12 at 00:56
  • @eouw0o83hf Now it's much clearer, thanks. Let's take it one step further though. While I can do `x = reader.GetList2(Extensions.GetDoubleOrNull);`, I still can't do `var x = reader.GetList2(reader.GetDoubleOrNull);`. I get a "type argument cannot be inferred from usage" error. – Moss Mar 08 '12 at 10:02
  • This is weird: `var x = reader.GetList(reader.GetDoubleOrNull);` also works. :S – Moss Mar 08 '12 at 10:49
  • I think it's a bug in the version 4 compiler. In version 5 this compiles just fine. (Of course we did not know that when the question was asked) – Andrew Savinykh Jul 26 '13 at 00:35
0

GetDoubleOrNull returns a double? GetList expects an IDataReader and a System.Func<string,int?>

The error message is a tad misleading

public double? blah(string s)

var x = reader.GetList(blah);

blah is a delegate in this call. In GetList(GetDoubleOrNull) it's the result of get doubleOrNull.

Good question though. Do post the answer wwhether I've helped or not.

Tony Hopkinson
  • 20,172
  • 3
  • 31
  • 39
  • The ``int`` must be a typo, changing it to ``double`` still gives the compiler error. – Jacek Gorgoń Mar 07 '12 at 12:45
  • 1
    You said "In GetList(GetDoubleOrNull) it's the result of get doubleOrNull" but this is not right. In this case `GetDoubleOrNull` is not being evaluated, it is being treated as a method group. – Chris Mar 07 '12 at 12:54
  • Never meant it was being evaluated. What's the signature of GetDoubleOrNull, compared to the argument of GetList. – Tony Hopkinson Mar 07 '12 at 23:21