【问题标题】:How to call DynamicObject.TryGetMember directly?如何直接调用 DynamicObject.TryGetMember?
【发布时间】:2011-07-15 10:18:20
【问题描述】:

我正在实现一个通用函数来从任意提供的动态对象中提取值,但不知道如何调用TryGetMember,因为它需要一个抽象的GetMemberBinder,因此我无法创建它。 示例...

public object GetValue(DynamicObject Source, string FieldName)
{
    object Result = null;
    GetMemberBinder Binder = x;  // What object must be provided?
    Binder.Name = FieldName;
    if (Source.TryGetMember(Binder, out Result))
       return Result;

    throw new Exception("The field '" + FieldName + "' not exists");
}

是否已经存在 GetMemberBinder 的具体后代可供使用?还是创建我自己的实现的指南?

【问题讨论】:

  • 我一定遗漏了一些东西……这整个方法不正是动态赋值运算符已经做的,除了不太可靠吗?
  • 我的问题是关于获取动态对象的字段值,同时不知道该字段是如何命名的。因此,我无法编写代码,例如“var MyValue = TheDynamicObject.TheField;”因为只有在运行时才能获得字段名称。我正在编写一个通用类来处理外部提供的动态对象。

标签: .net dynamic


【解决方案1】:

我不确定框架中是否有任何方法实际返回 GetMemberBinder,但这没关系 - 这不是按名称调用动态成员的正确方法。

您真正需要做的是创建一个呼叫站点。该方法如下所示:

static object GetDynamicMember(object obj, string memberName)
{
    var binder = Binder.GetMember(CSharpBinderFlags.None, memberName, obj.GetType(),
        new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
    var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);
    return callsite.Target(callsite, obj);
}

请注意,Binder.GetMember 创建了 CallSiteBinder不是 GetMemberBinder。只是为了100%清楚。如果对TryGetMember 的内部调用失败,此方法将抛出RuntimeBinderException,因此您无需检查结果。如果您不希望调用者看到 RuntimeBinderException,请将其包装在您自己的 try/catch 中。

动态调度是复杂的,至少相对于静态类型的反射而言。由于 CLR 实际上不是动态类型的,因此 C# 必须实际实例化一个 编译器 来确定如何执行成员/方法。那就是创建一个呼叫站点。据我所知,您必须这样做,这就是为什么每个Binder 方法都会返回一个CallSiteBinder 而您不能直接实例化任何活页夹。

请注意,DLR 会执行某种呼叫站点缓存,但我不确定自动缓存是否涵盖这种情况。您很有可能希望保存调用站点以供将来调用,以避免不断重新编译的开销。

附:如果您使用(或可以使用)ExpandoObject 而不是DynamicObject,请记住它实现了IDictionary&lt;string, object&gt;,因此您无需执行任何操作。只需将其转换为字典类型并检查该属性是否存在。如果我做的事情比简单地在运行时添加成员要复杂得多,即根据运行时绑定器更改实际行为,我只会使用 DynamicObject 而不是 ExpandoObject

【讨论】:

  • 自动缓存存储在调用站点和调用站点绑定器中,因此如果您至少不保留其中一个,您将获得非常糟糕的性能(我已经测试过)。这是impromptuinterface 为您所做的事情之一,它将尽可能重用它创建的调用站点。
  • 您显然对这个主题了解很多,但如果您继续发布该链接,您将前往start getting spam flags。如果它不总是一个超链接,至少它会少一些麻烦。
  • 我会记住这一点,但它是一个开源框架,旨在解决这个在一般情况下并非微不足道的特定问题。这种情况可能很简单,因为 get 成员可以始终具有相同的调用站点 func 签名,因此您只需要一个按成员名称索引的调用站点字典即可解决此缓存问题。
  • 不错的选择。我能够获取该值,缓存调用站点并将操作包含在不存在的成员的 try-catch 中。谢谢!
  • 即使调用成功,我也看到异常。
【解决方案2】:

你不直接调用 TryGetMember,你需要的是直接使用动态 api 通过使用 csharp 成员绑定和调用站点来获得相同的效果。

开源框架Dynamitey(通过 nuget)使这变得更加容易,因为它有一个静态方法来执行此操作。它适用于任何 IDynamicMetaObjectProvider ,而不仅仅是 DynamicObject 并且(它也适用于比反射更快的常规类型)。

return Dynamic.InvokeGet(Source, FieldName);

【讨论】:

  • 我不明白为什么这个答案被否决了。它现在正是这样做的,不是吗?
  • @jbtule,关于您的用户图片...很好!最佳个人资料?
  • 我猜是因为它需要一个外部库@TimBourguignon
猜你喜欢
  • 1970-01-01
  • 2011-01-12
  • 2012-12-25
  • 1970-01-01
  • 2022-07-09
  • 1970-01-01
  • 2013-05-21
  • 2014-04-08
  • 1970-01-01
相关资源
最近更新 更多