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 ;)
精彩评论