如何直接调用DynamicObject.TryGetMember?

时间:2011-03-14 23:52:49

标签: .net dynamic

我正在实现一个通用函数来从任意提供的动态对象中提取值,但不知道如何调用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已经存在的具体后代?或者是创建我自己的实现的指南?

2 个答案:

答案 0 :(得分:54)

我不确定框架中是否存在实际返回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 a GetMemberBinder。只是要100%清楚。如果对RuntimeBinderException的内部调用失败,此方法将抛出TryGetMember,因此您无需检查结果。如果您不希望呼叫者看到RuntimeBinderException,请将其包装在您自己的try / catch中。

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

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

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

答案 1 :(得分:14)

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

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

return Dynamic.InvokeGet(Source, FieldName);