1

I have overloaded methods, one generic and one non-generic. The two methods both receive a Linq Expression as single parameter:

public void Test(Expression<Action<char>> expr) {}

public void Test<T>(Expression<Func<char, T>> expr) {}

Now consider the following invocation:

var sb = new StringBuilder();
Test(c => sb.Append(c));

The compiler will pick the generic method since the Append() method does (unfortunately) return a StringBuilder. However, in my case I absolutely need the non-generic method to be called.

The following workaround shows that there is no type issue with the code (the non-generic call would be perfectly valid):

Expression<Action<char>> expr = c => sb.Append(c);
Test(expr);

However, I'd prefer not to declare a variable with an explicit type and instead somehow get the compiler to pick the non-generic method (just like I could tell it to use the generic method with explicit type parameters).

You can play with this at SharpLab.io.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • Give the methods different names if you want to choose which one is called, rather than having the compiler's betterness rules choose in ambiguous cases. – Servy Dec 27 '18 at 18:25
  • 1
    You can inline that extra variable: `Test((Expression>)(c => sb.Append(c)))` –  Dec 27 '18 at 18:28
  • 1
    @Amy The question specifically says *without* explicitly specifying the type. – Servy Dec 27 '18 at 18:28
  • 2
    @Servy Well, he says " I'd prefer not to declare a variable with an explicit type". I don't see where OP wants to avoid specifying the type entirely. –  Dec 27 '18 at 18:31
  • @Amy I would have assumed that an explicit cast would break things at runtime with an invalid cast exception, but looking at the generated IL this actually makes the compiler emit the correct Expression lambda, so that would indeed work. Still I'd prefer not to have the explicit type around as Servy correctly assumed. – Lucero Dec 27 '18 at 22:16

2 Answers2

4

This may seem like a workaround (because it is), but you can used a named parameter to clarify which method you are calling.

static public void Test(Expression<Action<char>> action) 
{
    Console.WriteLine("Test()");
}

static public void Test<T>(Expression<Func<char, T>> func) 
{
    Console.WriteLine("Test<T>()");
}

When you want the non-generic version, just provide the parameter name action: in the argument list.

static public void Main()
{
    var sb = new StringBuilder();
    Test(action: c => sb.Append(c) );
    Test(func: c => sb.Append(c) );
}

Output:

Test()
Test<T>()

This might be easier to use than writing out the expression cast.

Fiddle

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • 1
    Isn't the real issue that `c => sb.Append()` is passing `Append()` which has a `Func` signature not an `Action` signature? – Erik Philips Dec 27 '18 at 18:35
  • @ErikPhilips Yes, that is an issue, but I also think it is a given for the question ("in my case I absolutely need the non-generic method to be called"). – John Wu Dec 27 '18 at 18:43
  • This definitely solves the problem in the most elegant, readable and maintainable way. I just wanted to make sure I and future readers understood why the compiler was choosing one over the other. – Erik Philips Dec 27 '18 at 18:44
  • It is a good point @ErikPhilips and it got me wondering whether there is a contravariance relationship between Action and Func even though there is no direct inheritance relationship. Clearly a Func can take the place of an Action with the same inputs, since any return type will do when all that is needed is void. Do you have any thoughts on that? – John Wu Dec 27 '18 at 18:47
  • 1
    AFAIK, the problem is that C#'s function overload resolution doesn't take return types into account. https://stackoverflow.com/questions/2057146/compiler-ambiguous-invocation-error-anonymous-method-and-method-group-with-fun –  Dec 27 '18 at 18:48
  • 1
    I don't believe anyone could have answered it better than Eric. – Erik Philips Dec 27 '18 at 18:51
  • 1
    @JohnWu: That's a very source-code centric view. At the calling convention level, a function with a non-void return type cannot be substituted for a function with no return value, because the caller and callee don't agree on how to handle storage for the return value (callee demands that it exist, caller doesn't provide it nor clean it up). In MSIL this would manifest as a depth mismatch on the operand stack. – Ben Voigt Dec 27 '18 at 19:09
  • @JohnWu Nice one! Regarding contravariance relationship of Action and Func: there is none. The trick here is that the compiler will emit a Linq Expression instead of actual IL code when used this way; if you were casting the Expression to Expression you'd get an error. Apparently the Expression emit will match the parameter and return types of the lambda expression when trying to find the best match in the overload resolution and generic parameter infer process. – Lucero Dec 27 '18 at 22:23
1

You can use an empty method to swallow the return value of sb.Append. I wouldn't call this a workaround, since it just makes the compiler work normally, but it isn't totally clean and pretty either.

static public void NoValue(object value) {}

static public void Test(Expression<Action<char>> action) 
{
    Console.WriteLine("Test()");
}

static public void Test<T>(Expression<Func<char, T>> func) 
{
    Console.WriteLine("Test<T>()");
}

When you wrap the output in NoValue, the compiler correctly sees this as an Action, not a function.

static public void Main()
{
    var sb = new StringBuilder();
    Test(c => NoValue(sb.Append(c)) );
    Test(c => sb.Append(c) );
}

Output:

Test()
Test<T>()
Grax32
  • 3,986
  • 1
  • 17
  • 32