Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting an extension method group to a delegate with a generic type

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.

like image 504
Moss Avatar asked Mar 07 '12 12:03

Moss


Video Answer


1 Answers

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.

like image 83
eouw0o83hf Avatar answered Oct 12 '22 21:10

eouw0o83hf