Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DynamicObject.TryConvert not called when casting to interface type

Tags:

c#

dynamic

The following code throws an exception. TryConvert is not being called for the cast to interface. Why is this? Can I work around the problem?

using System.Dynamic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic test = new JsonNull();
            var ok = (string)test;
            // Next line throws:
            // Unable to cast object of type 'ConsoleApplication1.JsonNull' to type 'ConsoleApplication1.IFoo'.
            var fail = (IFoo)test;
        }
    }

    class JsonNull : DynamicObject
    {
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            result = null;
            return !binder.Type.IsValueType;
        }
    }

    interface IFoo { }
}
like image 871
Andrew Davey Avatar asked Aug 16 '10 12:08

Andrew Davey


3 Answers

I've found that if you change this line:

var fail = (IFoo)test; 

to this:

IFoo success = test;

it works as expected.

It seems that only an implicit conversion works in this case. Looks like a bug to me.

I also find it very annoying that this fails as well:

class Program {
  static void Main(string[] args) {
    dynamic test = new JsonNull();
    Fails(test);
  }
  static void Fails(IFoo ifoo) { }
}
// ...

Because it looks like it should use an implicit conversion too. Another bug?

like image 115
Jordão Avatar answered Nov 14 '22 17:11

Jordão


I suspect it's because in C# (and possibly .NET in general) you can't create a user-defined conversion to an interface type (in the same way that you can't create a user-defined conversion to/from a base/child type). Every interface conversion is therefore treated as a box or a reference conversion.

That really is just a guess though.

EDIT: On the other hand, I've just had a look at the code generated for:

dynamic d = ...;
IDisposable x = (IDisposable) d;

and it does generate a dynamic call via Binder.Convert, so it's not the C# compiler doing this. Hmm.

like image 23
Jon Skeet Avatar answered Nov 14 '22 17:11

Jon Skeet


This behavior is explained in the blog post On Dynamic Objects and DynamicObject by Chris Burrows:

"There is another snag in DynamicObject (..) when the call site’s underlying language provides some binding for any operation, that binding overrules any dynamic bindings that might exist.

(..) recall that there is always an explicit conversion from most class types to any interface type in C# (6.2.4, bullet 3), though they can fail. (..)

Just to expand on the interface conversion example a bit, it’s especially weird since if the conversion were implicit (say, try assigning to a local), then the dynamic conversion would have worked. Why? Because the C# binder would have said, “nope! no implicit conversion to IEnumerable,” and then the DynamicObject implementation would have let TryConvert do its thing."

like image 7
blueling Avatar answered Nov 14 '22 17:11

blueling