Compiler Ambiguous Invocation Error - Anonymous Method and Method Group with Func<> or Action

Compiler Ambiguous invocation error - anonymous method and method group with Func or Action

First off, let me just say that Jon's answer is correct. This is one of the hairiest parts of the spec, so good on Jon for diving into it head first.

Second, let me say that this line:

An implicit conversion exists from a method group to a compatible delegate type

(emphasis added) is deeply misleading and unfortunate. I'll have a talk with Mads about getting the word "compatible" removed here.

The reason this is misleading and unfortunate is because it looks like this is calling out to section 15.2, "Delegate compatibility". Section 15.2 described the compatibility relationship between methods and delegate types, but this is a question of convertibility of method groups and delegate types, which is different.

Now that we've got that out of the way, we can walk through section 6.6 of the spec and see what we get.

To do overload resolution we need to first determine which overloads are applicable candidates. A candidate is applicable if all the arguments are implicitly convertible to the formal parameter types. Consider this simplified version of your program:

class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}

So let's go through it line by line.

An implicit conversion exists from a method group to a compatible delegate type.

I've already discussed how the word "compatible" is unfortunate here. Moving on. We are wondering when doing overload resolution on Y(X), does method group X convert to D1? Does it convert to D2?

Given a delegate type D and an
expression E that is classified as a
method group, an implicit conversion
exists from E to D if E contains at
least one method that is applicable [...] to an
argument list constructed by use of
the parameter types and modifiers of
D, as described in the following.

So far so good. X might contain a method that is applicable with the argument lists of D1 or D2.

The compile-time application of a conversion from a method group E to a delegate type D is described in the following.

This line really doesn't say anything interesting.

Note that the existence of an implicit conversion from E to D does not guarantee that the compile-time application of the conversion will succeed without error.

This line is fascinating. It means that there are implicit conversions which exist, but which are subject to being turned into errors! This is a bizarre rule of C#. To digress a moment, here's an example:

void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));

An increment operation is illegal in an expression tree. However, the lambda is still convertible to the expression tree type, even though if the conversion is ever used, it is an error! The principle here is that we might want to change the rules of what can go in an expression tree later; changing those rules should not change the type system rules. We want to force you to make your programs unambiguous now, so that when we change the rules for expression trees in the future to make them better, we don't introduce breaking changes in overload resolution.

Anyway, this is another example of this sort of bizarre rule. A conversion can exist for the purposes of overload resolution, but be an error to actually use. Though in fact, that is not exactly the situation we are in here.

Moving on:

A single method M is selected corresponding to a method invocation of the form E(A) [...] The argument list A is a list of expressions, each classified as a variable [...] of the corresponding parameter in the formal-parameter-list of D.

OK. So we do overload resolution on X with respect to D1. The formal parameter list of D1 is empty, so we do overload resolution on X() and joy, we find a method "string X()" that works. Similarly, the formal parameter list of D2 is empty. Again, we find that "string X()" is a method that works here too.

The principle here is that determining method group convertibility requires selecting a method from a method group using overload resolution, and overload resolution does not consider return types.

If the algorithm [...] produces an error, then a compile-time error occurs. Otherwise the algorithm produces a single best method M having the same number of parameters as D and the conversion is considered to exist.

There is only one method in the method group X, so it must be the best. We've successfully proven that a conversion exists from X to D1 and from X to D2.

Now, is this line relevant?

The selected method M must be compatible with the delegate type D, or otherwise, a compile-time error occurs.

Actually, no, not in this program. We never get as far as activating this line. Because, remember, what we're doing here is trying to do overload resolution on Y(X). We have two candidates Y(D1) and Y(D2). Both are applicable. Which is better? Nowhere in the specification do we describe betterness between these two possible conversions.

Now, one could certainly argue that a valid conversion is better than one that produces an error. That would then effectively be saying, in this case, that overload resolution DOES consider return types, which is something we want to avoid. The question then is which principle is better: (1) maintain the invariant that overload resolution does not consider return types, or (2) try to pick a conversion we know will work over one we know will not?

This is a judgment call. With lambdas, we do consider the return type in these sorts of conversions, in section 7.4.3.3:

E is an anonymous function, T1 and T2
are delegate types or expression tree
types with identical parameter lists,
an inferred return type X exists for E
in the context of that parameter list,
and one of the following holds:

  • T1 has a return type Y1, and T2 has a return type Y2, and the conversion
    from X to Y1 is better than the
    conversion from X to Y2

  • T1 has a return type Y, and T2 is void returning

It is unfortunate that method group conversions and lambda conversions are inconsistent in this respect. However, I can live with it.

Anyway, we have no "betterness" rule to determine which conversion is better, X to D1 or X to D2. Therefore we give an ambiguity error on the resolution of Y(X).

Ambiguous C# method call with delegates

The long answer is at https://stackoverflow.com/a/2058854/1223597 (as richzilla pointed out).

The short answer is that the C# compiler team chose to make method group conversions (like Method(Method2)) ignore the return type (here of Method2). This gives them flexibility in how Expression trees are parsed. Unfortunately that means the compiler cannot implicitly choose between your 2 Method signatures.

When doing a lambda conversion, (Method(uri => Task.FromResult(uri))), the compiler team doesn't need to worry about expression tree parsing, so they do consider return types.

Ambiguity with Action and Func parameter

The reason is that the return type of a method is not part of its signature. Thus, while resolving the correct overload, the compiler only looks at the parameter of the method.

The easiest solution is to simply not use the implicit method group conversion. All of the following compile:

TaskManager.RunSynchronously<MyObject>(
x => fileMananager.BackupItems(x), package);

TaskManager.RunSynchronously<MyObject>(
(Action<MyObject>)fileMananager.BackupItems, package);

TaskManager.RunSynchronously<MyObject>(
new Action<MyObject>(fileMananager.BackupItems), package);

The first one is the most elegant of them, but it also is the only one with a - slight - runtime performance impact, because of an additional redirection. However, this impact is so small that you actually shouldn't care.

Why is this method invocation ambiguous?

This was an issue for all versions of C# up until it was fixed in v7.3. Return types were not taken into account during overload resolution. From the release notes (or the language proposal) for C# 7.3:


  1. For a method group conversion, candidate methods whose return type doesn't match up with the delegate's return type are removed from the set.

NSubstitute ambiguous call when following documentation example (but with async method)

The compiler error in your question looks like you are calling something that returns Task rather than Task<T>? This sample works for me:

public interface ISample {
Task DoStuff(string name);
}

[Test]
public async Task SampleTest()
{
var sample = Substitute.For<ISample>();
sample.DoStuff("test").Returns(x => { throw new Exception("doh"); });
// ...
}

I tend to get that error when I have a generic Task<T>, which can be fixed by specifying the return type in the .Returns() call like this:

public interface IRepository {
Task<int> FindId(string name);
}

[Test]
public async Task SampleTest2()
{
var sample = Substitute.For<IRepository>();
sample.FindId("test").Returns<int>(x => { throw new Exception("doh"); });
// ...
}

In the example above I've removed the ambiguity by using .Returns<int>(...) which will pick the first overload mentioned in the compiler error.

If that does not help could you post the signature of the method being tested?

Working with Actions and Task

In your example, you have not shown what value to pass to the method. Which makes it a bit difficult to know for sure what it is you are even asking. But I think you are asking for an illustration of how to call your method LongTask(), passing it the appropriate value, as the Task.Run() operation, and to provide this illustration using both the lambda syntax and non-lambda syntax.

Note: very little of this is actually specific to Task.Run(). It all applies any time you are dealing with passing a delegate to a method where the delegate needs to represent a method with a different signature from that used in the method you are calling. That Task.Run() is involved here is almost incidental.

Anyway…

Actually, since lambda expressions (in this usage) compile to anonymous methods, there are really three options:

"Old-school" anonymous method:

public void Run()
{
int someValue = 17;

Task q = Task.Run(delegate { LongTask(someValue); });
}

The above causes the compiler to create an anonymous method (delegate() { LongTask(someValue); }), and generate code to create a delegate instance to pass to the Task.Run() method. The type of the delegate is inferred from the usage (i.e. based on the one method overload that matches the anonymous method declaration).

Lambda anonymous method:

public void Run()
{
int someValue = 17;

Task q = Task.Run(() => LongTask(someValue));
}

The above is just like the previous example, except using the nominally more concise lambda syntax to declare the anonymous method. I say "nominally" because in this particular case, it's not really that much shorter. But there are other scenarios where the lambda syntax is much more convenient; typically, when the anonymous method is returning a value, since with lambda syntax the return statement is implicit.

Explicit:

class A
{
private readonly int _value;
private readonly Action<int> _action;

public A(int value, Action<int> action)
{
_value = value;
_action = action;
}

public void M() { _action(_value); }
}

public void Run()
{
int someValue = 17;

Task q = Task.Run((Action)(new A(someValue, LongTask).M));
}

The above relies on an explicitly written class to serve the same purpose as the compiler-generated class that the anonymous method options result in. Depending on the exact scenario, the compiler-generated class may be significantly different in implementation. But the behavior will be essentially the same: an instance of the class is created, in which the desired value is stored, and a method of the appropriate signature is included in the class, where the body of that method is the code you want executed.

(Aside: In this case, for conciseness, I've shown an implementation that actually takes a delegate instance instead of hard-coding the target object and method call. This is in fact a more reusable implementation than the compiler actually would generate, mainly because the compiler has to deal with a lot more complicated scenarios and this is an optimization that's only useful in a very narrow scenario, i.e. where there's exactly one method being called. The compiler would have also stored this and would have copied the body of the anonymous method to the generated method, using the stored this reference and any other captured variables as needed, e.g. to call the LongTask() method.)

In the above the variable _value is readonly, but in anonymous methods they would generally be mutable variables captured from the context of the anonymous method. To better emulate how an anonymous method typically would work, you'd want something more like this:

class A
{
public int value;
private readonly Action<int> _action;

public A(int value, Action<int> action)
{
this.value = value;
_action = action;
}

public void M() { _action(value); }
}

public void Run()
{
A a = new A(17, LongTask);

Task q = Task.Run((Action)a.M);

a.value = 19;
}

Note that in the above, the value is a public field. And the code actually modifies the variable after calling Task.Run(). This is akin to changing the value of someValue in the anonymous method examples (again, after calling Task.Run()), and has the same risk: if the assignment after the Task.Run() method is executed before the task can execute the method call, then you get a different value passed to the method than if the task can execute the method first.

Moral: always watch out for your captured variables, and make sure they have the lifetime and value you expect them to have.



By the way, the cast to the desired delegate type Action is required in these last two examples, because otherwise the Task.Run() overload to select is ambiguous to the compiler, between Task.Run(Action) and Task.Run(Func<Task>). Short version: the return type of the delegate signature is ignored when trying to convert from a method group to a delegate type and match it to a particular overload. For the longer version, see Compiler Ambiguous invocation error - anonymous method and method group with Func<> or Action.

Ambiguous call error with overloaded method that takes 2 delegates with different return types

Basically this is a corollary of how overloading is performed, and how method group conversions are resolved. I can try to wade through the spec to find the exact reasons if you like, but the bad news is that's just the way it is. It's possible there's a compiler bug in this case, but it's more likely that it's just a tricky bit of the spec.

I think that this SO question may be relevant, but I haven't checked yet.

Rather than resolve it using new, I would suggest take the option of using different method names. Overloading has all kinds of sneaky corner cases - this one is at least relatively harmless in that it's causing a compile-time error rather than picking the overload you don't want at execution time. Removing overloading is often a good thing :)

EDIT: I'm pretty sure that other SO question is relevant, actually. I suggest you get yourself a cup of coffee, the C# 4 spec, and then read Eric's answer very carefully. Then change the method names so you don't have to think about it any more.

Powershell create scriptblock for Func T instead of Action T

You need to cast ScriptBlock to right delegate type by yourself:

$t.Execute([Func[int]]{ 1 + 1 })


Related Topics



Leave a reply



Submit