How Does C# Choose with Ambiguity and Params

How does C# choose with ambiguity and params

It's easy to test - the second method gets called.

As to why - the C# language specification has some pretty detailed rules about how ambiguous function declarations get resolved. There are lots of questions on SO surrounding interfaces, inheritance and overloads with some specific examples of why different overloads get called, but to answer this specific instance:

C# Specification - Overload Resolution

7.5.3.2 Better function member

For the purposes of determining the
better function member, a
stripped-down argument list A is
constructed containing just the
argument expressions themselves in the
order they appear in the original
argument list.

Parameter lists for each of the
candidate function members are
constructed in the following way:

  • The expanded form is used if
    the function member was applicable
    only in the expanded form.

  • Optional parameters with no
    corresponding arguments are removed
    from the parameter list

  • The parameters are reordered
    so that they occur at the same
    position as the corresponding argument
    in the argument list.

And further on...

In case the parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are equivalent > (i.e. each Pi has an identity conversion to the corresponding Qi), the following
tie-breaking rules are applied, in order, to determine the better function member.

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

  • Otherwise, if MP is applicable in its normal form and MQ has a params array and is
    applicable only in its expanded form, then MP is better than MQ.

  • Otherwise, if MP has more declared parameters than MQ, then MP is better than MQ.
    This can occur if both methods have params arrays and are applicable only in their
    expanded forms.

The bolded tie-breaking rule seems to be what is applying in this case. The specification goes into detail about how the params arrays are treated in normal and expanded forms, but ultimately the rule of thumb is that the most specific overload will be called in terms of number and type of parameters.

Ambiguous method overloads when using generic type parameters

Second parameters are not identical. They are both Func<T, Task>, but T is different in each case.

First overload has this T source. That means when you do

Task<string> stringTask = Task.FromResult("sample");
stringTask.TeeAsync(...)

for first overload, T is Task<string>.

Second has this Task<T> asyncSource. So in above case, for second overload T is string.

Because you don't specify type of st here:

stringTask.TeeAsync(st => Task.CompletedTask).Wait();

st can be either Task<string> (first overload) or string (second). Compiler cannot know which one you meant. If you do:

stringTask.TeeAsync((string st) => Task.CompletedTask).Wait();

It will correctly choose second one. And if you do

stringTask.TeeAsync((Task<string> st) => Task.CompletedTask).Wait();

it will choose first.

Interesting that if you actually use st in a way which will allow compiler to deduce whether it's string or Task<string> - it will do that. For example this will compile and choose second overload:

// we don't specify st type, but using Length property
// which only exists on string
stringTask.TeeAsync(st => Task.FromResult(st.Length)).Wait();

And this will compile and choose first:

// we don't specify st type, but using Result property
// which only exists on Task<string>
stringTask.TeeAsync(st => Task.FromResult(st.Result)).Wait();

But if you use something that exists on both, it will again (correctly) fail to choose an overload:

// ToString() exists on both string and Task<string>
// so doesn't help compiler to choose
stringTask.TeeAsync(st => Task.FromResult(st.ToString())).Wait();

Ambiguity between generic and simple string in params

Simply put: where restrictions are not used while determining which method overload is to be used. So when you ignore that information it becomes not obvious which overload to use. You might argue that exact mach is better but it is not. Both methods can be called using string as parameter if you disregard this information.

Resolving Ambiguities with Optional Parameters and Named Arguments

The C# specification says in §7.5.3.2, regarding choosing a better overload:

If all parameters of [Method A] have a corresponding argument whereas default arguments need to be substituted for at least one optional parameter in [Method B] then [Method A] is better than [Method B].

When you specify a value for all parameters:

Person(1, 2.5, "Dark Ghost");

The above rule makes the first method a better candidate, and it is chosen as the correct overload.

When you don't:

Person(1, 46.5);

The rule does not apply, and the overload resolution is ambiguous.


You might say, why not choose the one with the least parameters? That seems fine at first, but causes a problem when you have something like this:

void Foobar(int a, string b = "foobar")
{
}

void Foobar(int a, int b = 0, int c = 42)
{
}

...

Foobar(1);

In this case there's no valid reason to choose the first one over the second. Thus you can only properly resolve this by supplying a value for all parameters.

params overload apparent ambiguity - still compiles and works?

This is clearly a bug in overload resolution.

It reproduces in C# 5 and C# 3 but not in Roslyn; I do not recall if we decided to deliberately take the breaking change or if this is an accident. (I don't have C# 4 on my machine right now but if it repros in 3 and 5 then it will in 4 also almost certainly.)

I have brought it to the attention of my former colleagues on the Roslyn team. If they get back to me with anything interesting I'll update this answer.

As I no longer have access to the C# 3 / 4 / 5 source code I am unable to say what the cause of the bug is. Consider reporting it on connect.microsoft.com.

Here's a much-simplified repro:

class P
{
static void M(params System.Collections.Generic.List<string>[] p) {}
static void M(params int[] p) {}
static void Main()
{
M();
}
}

It appears to have something to do with the genericity of the element type. Bizarrely, as Chris points out in his answer, the compiler chooses the more generic one! I would have expected the bug to be the other way, and choose the less generic one.

The bug is, incidentally, likely my fault, as I did a fair amount of the work on the overload resolution algorithm in C# 3. Apologies for the error.

UPDATE

My spies in the Roslyn team tell me that this is a known bug of long standing in overload resolution. There was a tiebreaker rule implemented that was never documented or justified that said that the type with the bigger generic arity was the better type. This is a bizarre rule with no justification, but it's never been removed from the product. The Roslyn team decided some time ago to take the breaking change and fix overload resolution so that it produces an error in this case. (I don't recall that decision, but we made a lot of decisions about this sort of thing!)

Why does .Net allow ambiguous methods such as these

how does the compiler know which method to call?

It follows the overload resolution rules listed in the C# language specification. In particular, in section 7.5.3.2 (looking at the C# 4 spec, but I believe C# 5 has the same numbering here) - "Better Function Member":

In case the parameter type seqquences are equivalent [...] the following tie-breaking ruls are applied, in order, to determine the better function member:

  • ...
  • Otherwise, if MP is applicable in its normal form and MQ has a params array and is applicable only in its expanded form, then MP is better than MQ.

So in your example, it's going to call the first overload.

Why does .Net allow this?

Because it can be useful in various cases (e.g. Console.WriteLine for a start).

I assume that in the IL, the resulting code is different and therefore allowed, but it shouldn't be, because you can get unexpected results.

You only get unexpected results if you don't expect the C# compiler to follow its specification. In that case, pretty much any behaviour can be unexpected.

Ambiguous method definitions, but want to maintain backwards compatibility

You can implement it as two functions instead:

public static T PollUntilReady<T>(Func<T> functionThatMight503)
{
return PollUntilReady(functionThatMight503, null);
}

public static T PollUntilReady<T>(
Func<T> functionThatMight503,
PollingOptions pollingOptions)
{
throw new NotSupportedException(); //Whatever
}

When called with only a single argument, the compiler can now resolve the ambiguity since it has a function to choose from that doesn't require any defaults.

This does mean that the default value for pollingOptions is now baked into your code rather than the calling code, which means that if you choose to change the default value later, older code will receive the new default even without recompilation.


This avoids ambiguity thanks to the overload resolution rule:

Otherwise if all parameters of MP have a corresponding argument whereas default arguments need to be substituted for at least one optional parameter in MQ then MP is better than MQ

from section 7.5.3.2 of the C# language spec.

C# Call is Ambiguous when passing a method group as delegate

You have a slight misinterpretation of how Func<Tin, Tout> works. Take a look at the docs:

public delegate TResult Func<in T, out TResult>(
T arg
)

The first argument is a parameter and the last argument is the return type.

When you look at this simplified version of your code:

internal class Program
{
public static void Main(string[] args)
{
new MyClass<double, double>(Method);
}

private static double Method(double d)
{
throw new NotImplementedException();
}
}

internal class MyClass<T, U>
{
public MyClass(Func<U, T> arg)
{
}

public MyClass(Func<U, Task<T>> arg)
{
}
}

You will notice that both arguments first specify the double, which is an argument, and then differ in return type: the T vs Task<T>.

However as we both know: overloading is done based on method name, parameter arity and parameter types. Return types are entirely ignored. In our case that means we have two Func<Tin, Tout> with double as argument and T vs Task<T> as return type.

Switching the arguments around compiles just fine:

internal class MyClass<T, U>
{
public MyClass(Func<T, U> arg)
{
}

public MyClass(Func<Task<T>, U> arg)
{
}
}

If you'll look in Visual Studio you'll notice that this method is now greyed out, which makes sense because the argument to Method is of type double and as such will always match T and not Task<T>.

So in order to test that it would now hit the correct overload if you pass in a different, asynchronous method, you can add in a second method:

private static double MethodAsync(Task<double> d)
{
throw new NotImplementedException();
}

and call it using

new MyClass<double, double>(MethodAsync);

You will now notice that the asynchronous Func<Task<T>, U>> is hit (which you can verify by doing a simply print to console from the constructors).


In short: you're trying to perform overload resolution on a return type, which obviously isn't possible.

Ambiguous between methods in csharp project

That is because of having a nullable decimal? and string which is also nullable..

Try doing this.. to indicate the compiler the appropriate overload to be called.

reportsParameters.Add(new ReportsParameter("IsCurrency", (string)null));

OR

reportsParameters.Add(new ReportsParameter("IsInactive", (decimal?)null));

as appropriate



Related Topics



Leave a reply



Submit