Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamic in Task.Run

I have a long running task with the same name in unrelated classes. I was trying to have this code in common method using dynamic. I am getting following error

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled by user code Message=Cannot implicitly convert type 'void' to 'object'

I tried to isolate the code to the following

class Program
{
    static void Main(string[] args)
    {
        MainAsync();
        Console.ReadKey();
    }
    static async void MainAsync()
    {
        var classA = new ClassA();
        var classB = new ClassB();
        await RunTask1(classA);
        await RunTask1(classB);
        await RunTask(classA);
        await RunTask(classB);
    }
    static async Task RunTask(dynamic val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
    static async Task RunTask1(ClassA val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
    static async Task RunTask1(ClassB val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
}
internal class ClassA
{
    public void CommonLongRunningTask()
    {
        Console.WriteLine("Class A CommonLongRunningTask");
    }
}
internal class ClassB
{
    public void CommonLongRunningTask()
    {
        Console.WriteLine("Class B CommonLongRunningTask");
    }
}

If I pass the object itself (RunTask1) instead of dynamic it works. I am trying to understand what is happening when I am passing in dynamic.

like image 764
Sarath Avatar asked Jan 09 '23 04:01

Sarath


2 Answers

I haven't yet been able to track it back to something in the language, but it appears you can't have an expression lambda with dynamic expression. Update: an expression involving a dynamic is always of type dynamic regardless of whether there's a void method call, see Language Aspects update

A statement lambda works:

private static async Task RunTask(dynamic val)
{
    await Task.Run(() =>
    {
        val.CommonLongRunningTask();
    });
}

Update:

Effectively what's going on here is when the compiler encounters this:

() => val.CommonLongRunningTask()

it interprets at it as the equivalent of:

() => {return val.CommonLongRunningTask();}

...since it doesn't know at compile-time that anything you call on val doesn't return anything. At run-time, it encounters the expression val.CommonLongRunningTask() which is of type void but cannot return that value to even the lowest common denominator object and thus throws the exception with message Cannot implicitly convert type 'void' to 'object'

You'd have the very same exception if you re-wrote your RunTask as follows:

    private static async Task RunTask(dynamic val)
    {
        await Task.Run(() =>
        {
            return val.CommonLongRunningTask();
        });
    }

Language Aspects

After a bit more research/discussion it appears section 7.5.2 of the C# spec details why this is happening. Essentially anything involving a dynamic is itself a dynamic type (because at compile time the compiler cannot know how things will be bound) and thus it views "val.CommonLongRunningTask()" as something that returns dynamic (and thus has a run-time error when it realizes it's void at run-time).

like image 54
Peter Ritchie Avatar answered Jan 11 '23 22:01

Peter Ritchie


Create a interface for your common method:

public interface ICommonTask
{
    void CommonLongRunningTask();
}

Implement this in both classes and use it instead:

static async Task RunTask(ICommonTask val)

class ClassA : ICommonTask

class ClassB : ICommonTask
like image 43
Frank Avatar answered Jan 11 '23 22:01

Frank