使用DynamicObject(IDynamicMetaObjectProvider)作为静态类型的组件会导致无限循环

时间:2013-07-11 18:10:51

标签: c#-4.0 dynamic

我正在尝试创建一个可以用作静态对象组件的动态对象。这是我想要完成的一个人为的例子。

以下是动态组件:

public class DynamicComponent : DynamicObject
{
    public override bool TryInvokeMember(
        InvokeMemberBinder binder, 
        object[] args, 
        out object result)
    {
        result = "hello";
        return true;
    }
}

这是一个继承自DynamicObject的类不可选的类......假设有一些我不得不继承的第三方类。

public class AStaticComponent : VendorLibraryClass, IDynamicMetaObjectProvider
{
    IDynamicMetaObjectProvider component = new DynamicComponent();

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        var result = component.GetMetaObject(parameter);

        return result;
    }
}

直接使用DynamicComponent有效:

dynamic dynamicComponent = new DynamicComponent();
Assert.AreEqual(dynamicComponent.AMethod(), "hello");

然而,转发GetMetaObjectAStaticComponent会导致某种形式的无限循环。

dynamic dynamicComponent = new AStaticComponent();
Assert.AreEqual(dynamicComponent.AMethod(), "hello"); //causes an infinite loop

任何人都知道为什么会这样吗?

如果在DynamicObject的行为中出现了一些我无法改变的事情,那么有人可以提供一些帮助来解决如何从头开始创建IDynamicMetaObjectProvider以完成基于组件的动态对象(只需获取一些内容)事情开始了吗?

2 个答案:

答案 0 :(得分:9)

我认为问题在于传递给Expression的{​​{1}}参数表示动态调用的目标(即当前对象)。您正在将外部对象传递给GetMetaObject上的调用,因此返回的元对象正在尝试解析对外部对象component.GetMetaObject的调用而不是自身,因此无限循环。

您可以创建自己的元对象,在绑定成员调用时委托给内部组件:

AMethod

答案 1 :(得分:1)

@ Lee的回答非常有用,如果没有它,我也不知道从哪里开始。但是从生产代码中使用它,我相信它有一个微妙的错误。

动态调用在调用站点缓存,Lee的代码生成DynamicMetaObject,它有效地表明内部处理对象是常量。如果您在代码中有一个位置,您在AStaticObject的实例上调用动态方法,稍后代码中的相同点会在AStaticObject的不同实例上调用相同的方法(即因为类型AStaticObject的变量现在有不同的值)然后代码将进行错误的调用,总是在代码运行期间从代码中该位置遇到的第一个实例调用处理对象上的方法。

这是一个类似的替换,关键的区别是使用Expression.Field告诉动态调用缓存系统处理对象依赖于父对象:

public class AStaticComponent : VendorLibraryClass, IDynamicMetaObjectProvider
{
    IDynamicMetaObjectProvider component = new DynamicComponent();

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DelegatingMetaObject(parameter, this, nameof(component));
    }

    private class DelegatingMetaObject : DynamicMetaObject
    {
        private readonly DynamicMetaObject innerMetaObject;

        public DelegatingMetaObject(Expression expression, object outerObject, string innerFieldName)
            : base(expression, BindingRestrictions.Empty, outerObject)
        {
            FieldInfo innerField = outerObject.GetType().GetField(innerFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
            var innerObject = innerField.GetValue(outerObject);
            var innerDynamicProvider = innerObject as IDynamicMetaObjectProvider;
            innerMetaObject = innerDynamicProvider.GetMetaObject(Expression.Field(Expression.Convert(Expression, LimitType), innerField));
        }

        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            return binder.FallbackInvokeMember(this, args, innerMetaObject.BindInvokeMember(binder, args));
        }
    }
}