有什么方法可以实现动态的“is”/“as”或者至少是伪造它吗?

问题描述 投票:0回答:4

我有一个类型

ConfigValue
,它通过
IDynamicMetaObjectProvider
的实现和自定义
DynamicMetaObject
实例公开动态接口。

当然,可以将这种类型的实例视为本机类型(类似于 XML 元素),但根据 XML 的内容和类型,它也可以视为任何其他对象类型的实例。它构建的对象(这是一个由小老我构建的专有 IOC)。

那么

<value>10</value>

可以被视为

ConfigValue
,但也可能是
string
short
double
等。转换是通过隐式或显式强制转换实现的,具体由调用语言指定。 XML 变得更加复杂,因为您可以触发构造函数、方法、属性获取等。

为了实现这一点,当然,我重写了

BindConvert
类型的
DynamicMetaObject
成员 - 如果运行时
ConfigValue
对象不支持转换,则会发生运行时错误(这很好)。

我刚刚开始编写一段代码,如果我可以安全地转换为目标类型,那就太好了,但如果这不起作用,则回退到其他逻辑 - 类似于:

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

但是,至少 C#(我确信可能是所有动态感知语言)不会为“as”或“is”操作发出任何动态绑定;大概是因为

DynamicMetaObject
没有进行此类测试的方法。因此,类型测试仅对静态类型信息执行,在这种情况下,它总是失败。

结果我不得不依赖相当丑陋的:

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

有什么想法可以避免这里的 try/catch/gulp 模式吗?我能想到的最好的就是

DynamicMetaObject
之上的东西;但必须先进行查询,然后再进行类型测试查询;这只会使代码进一步爆炸!

c# .net dynamic
4个回答
2
投票

我认为这是不可能的。

以这段代码为例:

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);
    }
}

如果我们使用 Reflector 反编译它,我们会发现强制转换能够被动态类型拦截的唯一原因是 C# 编译器为了支持它做了很多额外的工作:

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);
    }
}

相比之下,

as
is
调用只是编译为IL指令,没有来自C#编译器的任何额外魔力。

这也适合普通的铸造操作员;使用

as
而不是强制转换不会执行任何转换强制转换,因此永远不会更改底层对象的类型。


1
投票

is
as
运行时测试,适用于继承,因此它们不需要动态绑定,因为它们已经是动态的。即使没有动态关键字,您也永远无法使用
is
as
来测试隐式或显式转换,并且它们也永远不适用于
short
double
等值类型。

所以你的答案是没有必要伪造它,它们对于动态类型和静态类型的工作方式完全相同。您的

try
catch
可能是测试转换的最佳方法,捕获绑定错误是 DLR 在许多后备情况下已经在后台执行的操作。您可以在调试器中亲自查看是否在第一次出现异常时停止。

提高

try
catch
的最佳方法是指定确切的例外情况。

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

0
投票

为了代替不支持的事实,我编写了这个非常基本的静态方法来简化测试转换的操作。

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;
        }
    }
}

这是最顶层的代码;)


0
投票

由于

BindConvert
的结果是另一个 DynamicMetaObject,因此您始终可以返回一个 DynamicMetaObject,它表示返回 Exception(或其他一些哨兵值)的表达式,然后您可以检查它而不是捕获异常:

public DesiredType Foo(dynamic d)
 {
   dynamic result = (DesiredType)d;
   
   return result is DesiredType dt : dt ? default(DesiredType);
 }
© www.soinside.com 2019 - 2024. All rights reserved.