Согласно C# Спецификации, Вызовы методов , следующие правила используются для рассмотрения обобщенного c метода F
в качестве кандидата для вызова метода:
Метод имеет то же количество параметров типа метода, что и в списке аргументов типа,
и
После замены аргументов типа для соответствующих параметров типа метода все составные типы в списке параметров F
удовлетворяют своим ограничениям (удовлетворяющие ограничения), а список параметров F
применим по отношению к A
(Применимый элемент функции). A
- необязательный список аргументов.
Для выражения
Task.FromResult("foo").Map(x => $"hello {x}");
оба метода
public static T2 Map<T1, T2>(this T1 x, Func<T1, T2> f);
public static async Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f);
удовлетворяют следующим требованиям:
- они оба имеют два параметра типа;
их построенные варианты
// T2 Map<T1, T2>(this T1 x, Func<T1, T2> f)
string Ext.Map<Task<string>, string>(Task<string>, Func<Task<string>, string>);
// Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f)
Task<string> Ext.Map<string, string>(Task<string>, Func<string, string>);
удовлетворяют ограничениям типа (поскольку нет ограничения типа для Map
методов) и применяются в соответствии с необязательными аргументами (поскольку также не существует необязательных аргументов для Map
методов). Примечание: для определения типа второго аргумента (лямбда-выражения) используется вывод типа.
Таким образом, на этом шаге алгоритм рассматривает оба варианта в качестве кандидатов для вызова метода. В этом случае он использует разрешение перегрузки, чтобы определить, какой кандидат лучше подходит для вызова. Слова из спецификации:
Лучший метод из набора методов-кандидатов определяется с использованием правил разрешения перегрузки разрешения перегрузки. Если один лучший метод не может быть идентифицирован, вызов метода неоднозначен, и возникает ошибка времени привязки. При выполнении разрешения перегрузки параметры обобщенного c метода учитываются после подстановки аргументов типа (предоставленных или предполагаемых) для соответствующих параметров типа метода.
Выражение
// I intentionally wrote it as static method invocation.
Ext.Map(Task.FromResult("foo"), x => $"hello {x}");
можно переписать следующим образом, используя построенные варианты метода Map:
Ext.Map<Task<string>, string>(Task.FromResult("foo"), (Task<string> x) => $"hello {x}");
Ext.Map<string, string>(Task.FromResult("foo"), (string x) => $"hello {x}");
Разрешение перегрузки использует алгоритм Лучший член , чтобы определить, какой из этих двух методов лучше подходит для вызова метода.
Я прочитал этот алгоритм несколько раз и не нашел места, где алгоритм может определить метод Exp.Map<T1, T2>(Task<T1>, Func<T1, T2>)
как лучший метод для рассматриваемого вызова метода. В этом случае (когда лучший метод не может быть определен) возникает ошибка времени компиляции.
Подводя итог:
- алгоритм вызова метода рассматривает оба метода как кандидатов ;
- лучший алгоритм члена функции не может определить лучший метод для вызова.
Другой подход, помогающий компилятору выбрать лучший метод (как вы это делали в других обходных путях):
// Call to: T2 Map<T1, T2>(this T1 x, Func<T1, T2> f);
var a = Task.FromResult("foo").Map( (string x) => $"hello {x}" );
// Call to: async Task<T2> Map<T1, T2>(this Task<T1> x, Func<T1, T2> f);
var b = Task.FromResult(1).Map( (Task<int> x) => x.ToString() );
Теперь первый тип аргумента T1
явно определен, и двусмысленность не возникает.