Ответ 1
(Конечно, чтобы выйти из проблемы, просто скажите Task.Run((Func<int>)MyIntReturningMethod)
.)
Это не имеет ничего общего с Task
и т.д.
Одна из проблем, о которых стоит помнить, заключается в том, что когда присутствует очень много перегрузок, текст текста компилятора будет сосредоточен только на одной "паре" перегрузок. Так что это сбивает с толку. Причина в том, что алгоритм для определения наилучшей перегрузки учитывает все перегрузки, и когда этот алгоритм делает вывод о том, что наилучшей перегрузки не может быть найдена, что не создает определенную пару перегрузок для текста ошибки, поскольку все перегрузки могут (или не могут) были вовлечены.
Чтобы понять, что происходит, посмотрите на эту упрощенную версию:
static class Program
{
static void Main()
{
Run(() => 5); // compiles, goes to generic overload
Run(M); // won't compile!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
static int M()
{
return 5;
}
}
Как мы видим, это абсолютно не ссылается на Task
, но все же создает ту же проблему.
Обратите внимание, что преобразования анонимных функций и преобразования групп методов (по-прежнему) - это не то же самое. Подробности можно найти в спецификации языка С#.
Лямбда:
() => 5
на самом деле даже не конвертируется в тип System.Action
. Если вы попытаетесь сделать:
Action myLittleVariable = () => 5;
он будет с ошибкой CS0201: В качестве оператора можно использовать только назначение, вызов, приращение, декремент, ожидание и новые объектные выражения. Так что действительно ясно, какую перегрузку использовать с лямбдой.
С другой стороны, группа методов:
M
конвертируется как в Func<int>
, так и в Action
. Помните, что ему совершенно не разрешено возвращать возвращаемое значение, как и оператор:
M(); // don't use return value
действует само по себе.
Этот вопрос отвечает на вопрос, но я приведу лишний пример, чтобы сделать дополнительную мысль. Рассмотрим пример:
static class Program
{
static void Main()
{
Run(() => int.Parse("5")); // compiles!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
}
В этом последнем примере лямбда фактически конвертируется в оба типа делегатов! (Просто попробуйте удалить общую перегрузку.) Для правой стороны стрелки лямбда =>
есть выражение:
int.Parse("5")
который действителен как оператор сам по себе. Но в этом случае перегрузка может по-прежнему обнаруживать лучшую перегрузку. Как я сказал ранее, проверьте С# Spec.
Вдохновленный HansPassant и BlueRaja-DannyPflughoeft, вот один последний (я думаю) пример:
class Program
{
static void Main()
{
Run(M); // won't compile!
}
static void Run(Func<int> f)
{
}
static void Run(Func<FileStream> f)
{
}
static int M()
{
return 5;
}
}
Обратите внимание, что в этом случае абсолютно невозможно преобразовать int
5
в System.IO.FileStream
. Однако преобразование группы методов не выполняется. Это может быть связано с тем, что с двумя обычными методами int f();
и FileStream f();
, например, унаследованными некоторым интерфейсом от двух разных базовых интерфейсов, нет способа разрешить вызов f();
. Тип возврата не является частью сигнатуры метода в С#.
Я по-прежнему избегаю вводить Task
в свой ответ, поскольку это может дать неправильное представление о том, что это за проблема. Люди с трудом понимают Task
, и это относительно новое в BCL.
Этот ответ развился много. В конце концов, оказывается, что это действительно та же самая основная проблема, что и в потоке Почему Func<T>
неоднозначно с Func<IEnumerable<T>>
?. Мой пример с Func<int>
и Func<FileStream>
почти такой же ясный. Эрик Липперт дает хороший ответ в этой другой теме.