开发者

Any way to implement 'is'/'as' for dynamic or at least fake it?

I have a type ConfigValue that exposes a dynamic interface via an implementation of IDynamicMetaObjectProvider and custom DynamicMetaObject instance.

An instance of this type can be seen, of course, as it's native type - analogous to an XML Element - but it can also be seen as an instance of any other object type, according to the contents of the XML and what kind of object it builds (it's a proprietary IOC built by little old me).

So

<value>10</value>

Can be seen as a ConfigValue but also potentially a string, short, double etc. Conversion is achieved by implicit or explicit cast, as specified by the calling language. The XML gets more complicated because you can fire constructors, methods, property gets etc.

To achieve this, of course, I have overriden the BindConvert member of the DynamicMetaObject type - and if the conversion is unsupported by the ConfigValue object at runtime, then a runtime error occurs (which is fine).

I've just started writing a piece of code where it would be great if I could do a safe cast to the target type, but if that doesn't work fallback to some ot开发者_JS百科her logic - analogous to:

public DesiredType Foo(dynamic d)
{
  DesiredType dt = d as dt;
  if(dt != null)
    return dt;
  //TODO: Fallback logic to build dt from d
}

But, C# at least (and probably all dynamic-aware languages I'm sure) doesn't emit any dynamic binding for the 'as' or 'is' operations; presumably because DynamicMetaObject doesn't have a method for such a test. Thus the type test is just performed on the static type information which, in this case, always fails.

As a result I'm having to rely on the rather ugly:

public DesiredType Foo(dynamic d)
 {
   try
   {
      return (DesiredType)d;
   }
   catch(Exception)
   {
     //TODO: fallback logic
   }
 }

Any ideas how I can avoid the try/catch/gulp pattern here!? The best I can think of is something on top of DynamicMetaObject; but then that has to be queried for first, before then being queried again for the type test; which is just going to explode the code up even further!


I don't think it is possible.

Take for example, this code:

class Program
{
    static void Main(string[] args)
    {
        dynamic d = new object();

        var x = (Program)d;
        Console.WriteLine(x);

        var y = d as Program;
        Console.WriteLine(y);

        var z = d is Program;
        Console.WriteLine(z);
    }
}

If we decompile it using Reflector we see that the only reason that the cast is able to be intercepted by the dynamic type is that the C# compiler does a lot of extra work in order to support it:

class Program
{
    private static void Main(string[] args)
    {
        object d = new object();
        if (<Main>o__SiteContainer0.<>p__Site1 == null)
        {
            <Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, Program>>.Create(Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(Program), typeof(Program)));
        }
        Console.WriteLine(<Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d));

        Program y = d as Program;
        Console.WriteLine(y);

        bool z = d is Program;
        Console.WriteLine(z);
    }
}

In contrast, the as and is calls are simply compiled down to IL instructions, with no added magic from the C# compiler.

This fits in with the normal casting operators as well; using as rather than a cast will not perform any conversion casts, so will never change the type of the underlying object.


is and as are runtime tests that only work on inheritance, thus they don't need to dynamically bind because they are already dynamic. Even without the dynamic keyword you could never use is or as to test for implict or explicit conversions, and they never would work on value types like short and double either.

So your answer is that there is no need to fake it, they work exactly the same for dynamic types as static types. Your try catch is probably the best way to test the conversion, catching binding errors is what the DLR is already doing in the background for a lot of it's fallback cases. You can see for yourself in the debugger if you stop on first chance exceptions.

The best way to improve your try catch would be to specify the exact exception.

 catch(RuntimeBinderException)
   {
     //TODO: fallback logic
   }


In lieu of the fact that it's not supported, I've written this very basic static method to simplify the operation of testing the conversion.

public static class DynamicHelper
{
    public static TResult As<TResult>(dynamic obj) where TResult : class
    {
        if (obj == null)
            return null;
        try
        {
            return (TResult)obj;
        }
        catch (Exception)
        {
            return null;
        }
    }
}

It's top-drawer code that ;)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜