Reputation: 231
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
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.
EDIT: Checked this on mono, can somebody verify on VS
Upvotes: 12
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