3

I have this service interface.

public interface IService
{
    Task SetAsync(string key, string value, TimeSpan? expiration = null);
    Task SetAsync<T>(string applicationName, string key, T value, TimeSpan? expiration = null);
}

I would like to call the first method by doing so.

service.SetAsync("Key", "Value", TimeSpan.FromMinutes(1));

The method call matches 100% the contract of the first method. Yet the compiler chooses the second method by assuming TimeSpan is my generic type.

Why is this happening?

Nicolas Zawada
  • 497
  • 1
  • 3
  • 12
  • It's having defaults for the later parameters that throws things off. I'll try to find an apt duplicate – Damien_The_Unbeliever Dec 17 '20 at 08:19
  • 1
    An easy workaround to run other method is to specify its parameter name explicitly: `service.SetAsync(key:"Key", "Value", TimeSpan.FromMinutes(1));` ([fiddle](https://dotnetfiddle.net/IsNvVm)) – Sinatr Dec 17 '20 at 08:23
  • Try ```new Service().SetAsync("Key", "Value", new TimeSpan?());``` . Cause ```TimeSpan.FromMinutes(1)``` is ```TimeSpan``` not ```TimeSpan?``` – MichaelMao Dec 17 '20 at 08:24
  • 2
    @Damien_The_Unbeliever: I would expect not requiring the default parameter to make the *first* method preferable. Investigating... – Jon Skeet Dec 17 '20 at 08:26
  • If you pass a non null then it take first method or replace the ```TimeSpan?``` to ```TimeSpan``` on your method it will take first method too. – MichaelMao Dec 17 '20 at 08:27
  • @MichaelMao: I don't follow what you're saying. The OP is already passing a non-null value, but the compiler is apparently choosing the *second* method, not the first as you expect. – Jon Skeet Dec 17 '20 at 08:28
  • But I *believe* it's the requirement to use an implicit conversion which causes the second method to be "better". Currently looking up chapter and verse in the ECMA standard... – Jon Skeet Dec 17 '20 at 08:29
  • Some reference https://stackoverflow.com/questions/32892243/which-c-sharp-method-overload-is-chosen – MichaelMao Dec 17 '20 at 08:34

1 Answers1

8

This is following the rules for "Better function member" in the ECMA C# 5 standard, section 12.6.4.3:

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.

So at that point, we're comparing these two methods, effectively:

Task SetAsync(string key, string value, TimeSpan? expiration);
Task SetAsync<T>(string applicationName, string key, T value);

(In the generic method version, the expiration parameter does not have a corresponding argument, so it's removed from the parameter list.)

The argument list is "Key", "Value", TimeSpan.FromMinutes(1), and T is inferred to be TimeSpan.

Next:

Given an argument list A with a set of argument expressions { E1, E2, ..., EN } and two applicable function members MP and MQ with parameter types { P1, P2, ..., PN } and { Q1, Q2, ..., QN }, MP is defined to be a better function member than MQ if

  • for each argument, the implicit conversion from EX to QX is not better than the implicit conversion from EX to PX, and
  • for at least one argument, the conversion from EX to PX is better than the conversion from EX to QX.

For the first two parameters, there's no difference - because they're string everywhere.

For the third parameter, the conversion for the non-generic method is from TimeSpan to TimeSpan?, and for the generic method it's from TimeSpan to TimeSpan (because T is TimeSpan).

The identity conversion of TimeSpan to TimeSpan is "better" (as per the rules of 12.6.4.4) than the implicit conversion of TimeSpan to TimeSpan?, so for that parameter, the generic method is "better"... and there's no parameter for which the non-generic method is "better", therefore overall the generic method is better.

If neither method had been found to be "better" in this stage (e.g. due to a parameter type of TimeSpan instead of TimeSpan? for the optional parameter) then the non-generic method would have been determined to be "better" by the tie-break rules following this stage. But by that point, the generic method has already been selected.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • A-ha, following the rules, it all makes sense now. Thank you for clearing this up. I would be lying if I said this didn't lose me an hour of sleep last night. It's especially the rules of 12.6.4.4 that make this easy confusable. My gut feeling tells me I would have to specify `` to make the compiler choose the generic method. But that's where 12.6.4.4 comes in. – Nicolas Zawada Dec 17 '20 at 09:17