Подтвердить что ты не робот

С#.net Почему Task.Run, похоже, обрабатывает Func <T> иначе, чем другой код?

Новый статический метод Task.Run, который часть .NET 4.5, похоже, не ведет себя так, как можно было бы ожидать.

Например:

Task<Int32> t = Task.Run(()=>5);     

компилируется отлично, но

Task<Int32> t = Task.Run(MyIntReturningMethod);
...
public Int32 MyIntReturningMethod() {
  return (5);
  }

жалуется, что MyIntReturningMethod возвращает неправильный тип.

Возможно, я просто не понимаю, какая перегрузка Task.Run вызывается. Но, на мой взгляд, мой лямбда-код выше выглядит как Func<Int32>, а MyIntReturningMethod определенно совместим с Func<Int32>

Любые идеи о том, что происходит? Майкл

4b9b3361

Ответ 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> почти такой же ясный. Эрик Липперт дает хороший ответ в этой другой теме.

Ответ 2

Это должно было быть исправлено в .Net 4.0, но Task.Run() новичок в .Net 4.5

.NET 4.5 имеет свою собственную неоднозначность при перегрузке, добавив метод Task.Run(Func<Task<T>>). И поддержка async/await в С# версии 5. Что позволяет неявное преобразование от T foo() до Func<Task<T>>.

Этот синтаксический сахар, который довольно сладкий для async/ждет, но создает полости здесь. Отсутствие ключевого слова async в объявлении метода не рассматривается в выборе метода перегрузки метода, что открывает еще одну коробку pandora неудач, когда программисты забывают использовать async, когда они хотели. В противном случае следует обычное соглашение С#, что для выбора метода перегрузки рассматривается только имя метода и аргументы в сигнатуре метода.

Для разрешения двусмысленности требуется явно использовать тип делегата.

Ответ 3

Когда вы передаете Func<TResult> в метод Run<TResult>(Func<TResult>), вам не нужно указывать общий атрибут метода, потому что он может его вывести. Ваша лямбда делает этот вывод.

Однако ваша функция на самом деле не является Func<TResult>, тогда как лямбда была.

Если вы выполняете Func<Int32> f = MyIntReturningMethod, он работает. Теперь, если вы укажете Task.Run<Int32>(MyIntReturningMethod), вы ожидаете, что он тоже будет работать. Однако он не может решить, следует ли разрешить перегрузку Func<Task<TResult>> или перегрузку Func<TResult>, и это не имеет особого смысла, поскольку очевидно, что метод не возвращает задачу.

Если вы скомпилируете что-то простое, например:

void Main()
{
    Thing(MyIntReturningMethod);
}


public void Thing<T>(Func<T> o)
{
    o();
}

public Int32 MyIntReturningMethod()
{
return (5);
}

ИЛ выглядит так....

IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldftn       UserQuery.MyIntReturningMethod
IL_0009:  newobj      System.Func<System.Int32>..ctor
IL_000E:  call        UserQuery.Thing

(Некоторые из дополнительных материалов связаны с дополнениями LINQ Pad... как часть UserQuery)

IL выглядит одинаково, как если бы вы делали явное приведение. Таким образом, похоже, что компилятор действительно не знает, какой метод использовать. Таким образом, он не знает, что делать для автоматического создания.

Вы можете просто использовать Task.Run<Int32>((Func<Int32>)MyIntReturningMethod), чтобы немного помочь. Хотя я согласен, что это похоже на компилятор, с которым можно справиться. Поскольку Func<Task<Int32>> не совпадает с Func<Int32>, поэтому не имеет смысла, что они путают компилятор.

Ответ 4

Похоже на проблему разрешения перегрузки. Компилятор не может определить, какую перегрузку вы вызываете (потому что сначала он должен найти правильный делегат для создания, чего он не знает, потому что это зависит от перегрузки, которую вы вызываете). Он должен был бы угадать и проверить, но я предполагаю, что это не тот умный.

Ответ 5

Подход Tyler Jensen работает для меня.

Кроме того, вы можете попробовать это, используя выражение лямбда:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}

Ответ 6

Здесь мой удар по нему:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(new Func<int>(MyIntReturningMethod));
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}