Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload precedence between Expression<Action> and Expression<Action<T>>

The short version:

What is the best way of overloading two methods, when one accepts an Expression<Action>, and another accepts an Expression<Action<T>>?

The longer version:

Let's say I have the following methods in a (badly designed) class:

void Main()
{
    Foo(t => Bar(t));
}

void Foo(Expression<Action> action)
{
    "Method 1!".Dump();
}

void Foo<T>(Expression<Action<T>> action)
{
    "Method 2!".Dump();
}

void Bar(String thing)
{
    // Some bar-like thing
}

Now, it may be me being particularly dim, but I would expect 'Method 2' to be invoked from the Main method.

The only constraint on this is that I need to pass an Expression<...> as we do some magic elsewhere based on the expression tree.

My rationale is something like this:

  1. Generics aren't real - they're a compiler trick
  2. An Action<T> is actually a very different delegate type to an Action
  3. Therefore Method 2 should be invoked, as the compiler has all the information it requires to make the inference.

What actually happens is that I get a compiler error to the effect that I'm trying to pass an argument to an Action which accepts no arguments... ie. Method 1 is being targeted.

On a side note, this works as expected if I specify the generic parameter explicitly like this:

Foo<String>(t => Bar(t));

Your thoughts on this would be appreciated!

like image 960
Dan Avatar asked Jun 22 '15 14:06

Dan


1 Answers

Currently, neither of your methods is applicable - because type inference can't infer T for your second method, and the first method is invalid because your anonymous function has a parameter (unlike Action). The compiler is reporting an error as if it had performed overload resolution and picked the first method, but it's one of those cases where the error message doesn't really tell the whole story.

If you change the signature of method 1 to:

Foo(Expression<Action> action, string item)

and the signature of method 2 to:

Foo<T>(Expression<Action<T>> action, T item)

then type inference would work, and the second method would be invoked (as the first method isn't applicable).

If both methods were applicable (e.g. after the above change, you changed the first parameter of the nongeneric method to Expression<Action<string>>), it would end up in a tie-break in terms of normal "argument to parameter type" conversions - but then the first tie-break rule (in section 7.5.3.2 of the C# 5 specification) is:

  • If MP is a nongeneric method and MQ is a generic method, then MP is better than MQ.

In other words, non-generic methods are favoured over generic ones, in overload resolution.

In terms of fixing your current code - it's hard to know how to do that without having more context of what you're trying to achieve.

like image 61
Jon Skeet Avatar answered Sep 19 '22 04:09

Jon Skeet