Vakun
Vakun

Reputation: 231

Unpredictible behaviour in c# dynamic

I've found a bug (feature?) during learning dynamic in C#. Can anyone explain me, why do I have an exception??

static class Program
{
    public static void Main(string[] args)
    {
        dynamic someObj = ConstructSomeObj((Action)(() => Console.WriteLine("wtf")));

        var executer = someObj.Execute;
        executer();         // shows "wtf"
        someObj.Execute();  // throws RuntimeBinderException 

        Console.ReadKey();
    }

    static dynamic ConstructSomeObj(dynamic param) 
        => new { Execute = param };
}

Note: typeof both exectuer and someObj is dynamic

Upvotes: 18

Views: 739

Answers (2)

csharpfolk
csharpfolk

Reputation: 4280

Let's look at following code:

using System;
using System.Collections.Generic;


public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("first");

        // works perfectly!!!
        dynamic foo = new { x=(Action)(() => Console.WriteLine("ok")) };
        foo.x();

        // fails
        dynamic foo2 = new { x=(object)(Action)(() => Console.WriteLine("ok2")) };
        foo2.x();

    }
}

dynamic uses reflection to access objects method and fields and since it cannot know exact types it must rely on type information present in objects on which it operate.

When field x in anonymous type is properly typed as delegate invocation foo.x() works because dynamic can see that field value is delegate.

When you use

static dynamic ConstructSomeObj(dynamic param) 
    { return new { x = param }; }

to create anonymous class you created class with field x of type object (dynamic is object behind the scenes). When you call obj.x dynamic sees that field type is an object and it does't bother to check to what exact type this field points. And since object doesn't have Invoke() method like delegates it throws exception. If you change method parameter type to Action it will work.

I guess this decision to check field type instead of type of value that field contains was taken to provide better performance. In other words when you check field type CallSite class generated by dynamic can be cached and reused later.

References: https://github.com/mono/mono/blob/ef407901f8fdd9ed8c377dbec8123b5afb932ebb/mcs/class/Microsoft.CSharp/Microsoft.CSharp.RuntimeBinder/Binder.cs

https://github.com/mono/mono/blob/ef407901f8fdd9ed8c377dbec8123b5afb932ebb/mcs/class/Microsoft.CSharp/Microsoft.CSharp.RuntimeBinder/CSharpInvokeMemberBinder.cs

EDIT: Checked this on mono, can somebody verify on VS

Upvotes: 12

Robert K
Robert K

Reputation: 106

ok, interesting. This 2 Lines would work:

Task.Run(someObj.Execute);  
((Action)someObj.Execute)();

It seems that the compiler does accept the () for dynamic types always and at runtime the CLR only looks 'one level deep'. So you can help here by adding explicit cast or do the cast implicit with the Task.Run(). If this is a feature or a bug!? ... no idea ;-) ...

Upvotes: 3

Related Questions