Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delegate as first param to an Extension Method

Ladies and Gents,

I recently tried this experiment:

static class TryParseExtensions
{
    public delegate bool TryParseMethod<T>(string s, out T maybeValue);
    public static T? OrNull<T>(this TryParseMethod<T> tryParser, string s) where T:struct 
    {
        T result;
        return tryParser(s, out result) ? (T?)result : null;
    }
}

// compiler error "'int.TryParse(string, out int)' is a 'method', which is not valid in the given context"
var result = int.TryParse.OrNull("1");  // int.TryParse.OrNull<int>("1"); doesnt work either

// compiler error: type cannot be infered....why?
var result2 = TryParseExtensions.OrNull(int.TryParse, "2"); 

// works as expected
var result3 = TryParseExtensions.OrNull<int>(int.TryParse, "3");
      var result4 = ((TryParseExtensions.TryParseMethod<int>)int.TryParse).OrNull("4");

I am wondering two things:

  • Why can the compiler not infer the "int" type parameter?

  • Do I understand correctly that extensions methods do not get discovered on Delegate types, as I guess they arent really of that type (but are a "Method") that only happen to match the delegates signature? As such a cast solves this. Would it be infeasable to enable scenario 1 to work (not this one specifically of course, but in general)? I guess from a language/compiler perspective and would it actually be useful, or am I just (attempting to) wildly abusing things here?

Looking forward to some insights. Thnx

like image 296
chrisaut Avatar asked May 31 '11 13:05

chrisaut


People also ask

What modifier is required before the first parameter of an extension method?

You can add the ref modifier to the first argument of an extension method. Adding the ref modifier means the first argument is passed by reference. This enables you to write extension methods that change the state of the struct being extended.

What is extension method in C++?

In object-oriented computer programming, an extension method is a method added to an object after the original object was compiled. The modified object is often a class, a prototype or a type. Extension methods are features of some object-oriented programming languages.

How do you override an extension method?

Extension methods cannot be overridden the way classes and instance methods are. They are overridden by a slight trick in how the compiler selects which extension method to use by using "closeness" of the method to the caller via namespaces.


2 Answers

You have a number of questions here. (In the future I would recommend that when you have multiple questions, split them up into multiple questions rather than one posting with several questions in it; you'll probably get better responses.)

Why can the compiler not infer the "int" type parameter in:

TryParseExtensions.OrNull(int.TryParse, "2");  

Good question. Rather than answer that here, I refer you to my 2007 article which explains why this did not work in C# 3.0:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

Summing up: fundamentally there is a chicken-and-egg problem here. We must do overload resolution on int.TryParse to determine which overload of TryParse is the intended one (or, if none of them work, what the error is.) Overload resolution always tries to infer from arguments. In this case though, it is precisely the type of the argument that we are attempting to infer.

We could come up with a new overload resolution algorithm that says "well, if there's only one method in the method group then pick that one even if we don't know what the arguments are", but that seems weak. It seems like a bad idea to special-case method groups that have only one method in them because that then penalizes you for adding new overloads; it can suddenly be a breaking change.

As you can see from the comments to that article, we got a lot of good feedback on it. The best feedback was got was basically "well, suppose type inference has already worked out the types of all the argument and it is the return type that we are attempting to infer; in that case you could do overload resolution". That analysis is correct, and changes to that effect went into C# 4. I talked about that a bit more here:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

Do I understand correctly that extensions methods do not get discovered on delegate types, as I guess they arent really of that type (but are a "Method") that only happen to match the delegates signature?

Your terminology is a bit off, but your idea is correct. We do not discover extension methods when the "receiver" is a method group. More generally, we do not discover extension methods when the receiver is something that lacks its own type, but rather takes on a type based on its context: method groups, lambdas, anonymous methods and the null literal all have this property. It would be really bizarre to say null.Whatever() and have that call an extension method on String, or even weirder, (x=>x+1).Whatever() and have that call an extension method on Func<int, int>.

The line of the spec which describes this behaviour is :

An implicit identity, reference or boxing conversion [must exist] from [the receiver expression] to the type of the first parameter [...].

Conversions on method groups are not identity, reference or boxing conversions; they are method group conversions.

Would it be infeasable to enable scenario 1 to work (not this one specifically of course, but in general)? I guess from a language/compiler perspective and would it actually be useful, or am I just (attempting to) wildly abusing things here?

It is not infeasible. We've got a pretty smart team here and there's no theoretical reason why it is impossible to do so. It just doesn't seem to us like a feature that adds more value to the language than the cost of the additional complexity.

There are times when it would be useful. For example, I'd like to be able to do this; suppose I have a static Func<A, R> Memoize<A, R>(this Func<A, R> f) {...}:

var fib = (n=>n<2?1:fib(n-1)+fib(n-2)).Memoize();

Instead of what you have to write today, which is:

Func<int, int> fib = null;
fib = n=>n<2?1:fib(n-1)+fib(n-2);
fib = fib.Memoize();

But frankly, the additional complexity the proposed feature adds to the language is not paid for by the small benefit in making the code above less verbose.

like image 91
Eric Lippert Avatar answered Nov 02 '22 23:11

Eric Lippert


The reason for the first error:
int.TryParse is a method group, not an object instance of any type. Extension methods can only be called on object instances. That's the same reason why the following code is invalid:

var s = int.TryParse;

This is also the reason why the type can't be inferred in the second example: int.TryParse is a method group and not of type TryParseMethod<int>.

I suggest, you use approach three and shorten the name of that extension class. I don't think there is any better way to do it.

like image 2
Daniel Hilgarth Avatar answered Nov 02 '22 22:11

Daniel Hilgarth