Search code examples
c#async-awaitactionfunc

Precompile Exception: Delegate 'Action' does not take 1 arguments


I'm trying to repair some code that I cloned from a public repo. It's an async method that's missing an await operator:

public async Task<IEnumerable<JsonPatchOperation>> GetRemoveAllRelationsOperations(IBatchMigrationContext batchContext, WorkItem targetWorkItem)
{
  return targetWorkItem.Relations?.Select((r, index) => MigrationHelpers.GetRelationRemoveOperation(index));
}

I'm trying this:

public async Task<IEnumerable<JsonPatchOperation>> GetRemoveAllRelationsOperations(IBatchMigrationContext batchContext, WorkItem targetWorkItem)
{
  return await Task.Run(o => targetWorkItem.Relations?.Select((r, index) => MigrationHelpers.GetRelationRemoveOperation(index)));
}

...but I'm getting an error in the IDE:

Delegate 'Action' does not take 1 arguments

I found some similar discussions, but unfortunately none of them quite address the lambda syntax:

It appears the precompiler is interpreting the input as an Action when it should be seeing it as a Func instead. But I thought that the statement o => ... could indicate either.

I'm not familiar enough with C# to be able to work this one out. Can someone assist?

How do I tell the precompiler that I want to send a Func instead of an Action?


Solution

  • Well, the reason for the compilation error, is that Task.Run accepts a (non-generic) Action, which is a delegate that accepts no arguments.

    You have tried to call Task.Run with a lambda accepting an argument o, so changing to this will remove the error:

    Task.Run(() =>
    

    The parentheses () denote no arguments within a lambda expression.

    Having said that, wrapping a synchronous function in Task.Run is an anti-pattern.

    If your method is completely synchronous, you should ideally expose it as such:

    public IEnumerable<JsonPatchOperation> GetRemoveAllRelationsOperations(IBatchMigrationContext batchContext, WorkItem targetWorkItem)
    {
        return targetWorkItem.Relations?
            .Select((r, index) => MigrationHelpers.GetRelationRemoveOperation(index));
    }
    

    If you cannot change the signature, for example if you are implementing an interface, then use Task.FromResult instead:

    public Task<IEnumerable<JsonPatchOperation>> GetRemoveAllRelationsOperations(IBatchMigrationContext batchContext, WorkItem targetWorkItem)
    {
        return Task.FromResult(targetWorkItem.Relations?
            .Select((r, index) => MigrationHelpers.GetRelationRemoveOperation(index)));
    }
    

    This just wraps the synchronous result in a Task object, rather than forcing the lambda to run on the threadpool.