5

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

这是动态组件:

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

但是,转发GetMetaObject通过AStaticComponent会导致某种形式的无限循环。

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

有谁知道为什么会这样?

如果它是一些DynamicObject我无法改变的行为,有人可以提供一些帮助,说明如何IDynamicMetaObjectProvider从头开始创建一个基于组件的动态对象(只是让事情开始的东西)?

4

2 回答 2

9

I think the problem is that the Expression parameter passed to GetMetaObject represents the target of the dynamic invocation (i.e. the current object). You are passing the outer object to the call on component.GetMetaObject, so the returned meta object is trying to resolve the call to AMethod on the outer object instead of itself, hence the infinite loop.

You can create your own meta object which delegates to the inner component when binding member invocations:

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

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DelegatingMetaObject(component, parameter, BindingRestrictions.GetTypeRestriction(parameter, this.GetType()), this);
    }

    private class DelegatingMetaObject : DynamicMetaObject
    {
        private readonly IDynamicMetaObjectProvider innerProvider;

        public DelegatingMetaObject(IDynamicMetaObjectProvider innerProvider, Expression expr, BindingRestrictions restrictions)
            : base(expr, restrictions)
        {
            this.innerProvider = innerProvider;
        }

        public DelegatingMetaObject(IDynamicMetaObjectProvider innerProvider, Expression expr, BindingRestrictions restrictions, object value)
            : base(expr, restrictions, value)
        {
            this.innerProvider = innerProvider;
        }

        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            var innerMetaObject = innerProvider.GetMetaObject(Expression.Constant(innerProvider));
            return innerMetaObject.BindInvokeMember(binder, args);
        }
    }
}
于 2013-07-13T22:01:23.033 回答
3

@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));
        }
    }
}
于 2017-09-28T17:14:25.243 回答