并不总是调用IDynamicMetaObjectProvider.GetMetaObject

Vag*_*lov 6 c# dynamic

我正在努力解决实现IDynamicMetaObjectProvider接口的类的一个非常奇怪的问题.根据文档,每次尝试对此类的实例进行动态绑定时,都会调用GetMetaObject来解析动态绑定值.

但我所经历的是一种神秘感.看看这段代码:

public class DataEntry : Dictionary<string, object>
{
    public DataEntry(IDictionary<string, object> entry)
        : base(entry)
    {
    }
}

public class DynamicDataEntry : DataEntry, IDynamicMetaObjectProvider
{
    internal DynamicDataEntry()
        : base(new Dictionary<string, object>())
    {
    }

    public DynamicDataEntry(IDictionary<string, object> entry)
        : base(entry)
    {
    }

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DynamicEntryMetaObject(parameter, this);
    }

    private class DynamicEntryMetaObject : DynamicMetaObject
    {
        internal DynamicEntryMetaObject(
            Expression parameter,
            DynamicDataEntry value)
            : base(parameter, BindingRestrictions.Empty, value)
        {
        }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            var methodInfo = this.GetType().GetMethod("GetEntryValue", BindingFlags.Instance | BindingFlags.NonPublic);
            var arguments = new Expression[]
            {
                Expression.Convert(Expression.Constant(base.Value), typeof (DynamicDataEntry)),
                Expression.Constant(binder.Name)
            };
            Expression objectExpression = Expression.Call(Expression.Constant(this), methodInfo, arguments);

            return new DynamicMetaObject(
                objectExpression,
                BindingRestrictions.GetTypeRestriction(Expression, this.RuntimeType));
        }

        private object GetEntryValue(DynamicDataEntry entry, string propertyName)
        {
            return entry[propertyName];
        }
    }
}

// And here is the test:

    [Test]
    public void Test()
    {
        var dict = new[]
        {
            new Dictionary<string, object>() {{"StringProperty", "a"}, {"IntProperty", 1}},
            new Dictionary<string, object>() {{"StringProperty", "b"}, {"IntProperty", 2}},
        };

        var values = (dict.Select(x => new DynamicDataEntry(x)) as IEnumerable<dynamic>).ToArray();
        for (int index = 0; index < values.Count(); index++)
        {
            // GetMetaObject is called only first time for the line below, so it is "a" for both iterations! WHY?!!
            var s = values[index].StringProperty;

            switch (index)
            {
                case 0:
                    Assert.AreEqual("a", values[index].StringProperty);
                    Assert.AreEqual("a", s);
                    break;
                case 1:
                    Assert.AreEqual("b", values[index].StringProperty);
                    Assert.AreEqual("b", s);
                    break;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

当我调试代码时,我可以看到循环中第一行的StringProperty上的GetMetaObject总是在第一次迭代时被调用,但是在下一次迭代中没有调用GetMetaObject - 而是DLR执行值[index]的表达式上一次迭代,因此将StringProperty评估为"a".但Assert.AreEqual调用触发GetMetaObject执行,StringProperty正确评估为"b".

这种行为让我疯了,我无法理解可能导致它的原因.有没有人有任何想法?

更新我收到一个建议,从DynamicObject而不是IDynamicMetaObjectProvider派生我的类.长话短说:我知道DynamicObject,但它不适合我的情况.我只发布了一个简单的例子来说明发生了什么.真正的实现需要从除DataEntry之外的另一个类派生,并且这样的派生是必不可少的,所以我必须实现IDynamicMetaObjectProvider,即使它更有效.

Vag*_*lov 5

我通过重写BindGetMember中使用的表达式找到了解决问题的方法.以下是有效的代码.

重要的不同之处在于前面的代码使用Expression.Constant(this)来引用从DynamicMetaObject派生的类的实例.我发现样本使用了相当神秘的表达式Expression.Convert(Expression,LimitType).更新版本适用于所有测试.

我必须说IDynamicMetaObjectProvider周围的东西很难(或没有)记录,我仍然没有解释为什么我的原始代码不能与遍历IEnumerable结合使用.感谢一些博客文章,我设法以在我的所有场景中运行的方式重写它.

public class DataEntry : Dictionary<string, object>
{
    public DataEntry(IDictionary<string, object> entry)
        : base(entry)
    {
    }

    private object GetEntryValue(string propertyName)
    {
        return base[propertyName];
    }
}

public class DynamicDataEntry : DataEntry, IDynamicMetaObjectProvider
{
    internal DynamicDataEntry()
        : base(new Dictionary<string, object>())
    {
    }

    public DynamicDataEntry(IDictionary<string, object> entry)
        : base(entry)
    {
    }

    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DynamicEntryMetaObject(parameter, this);
    }

    private class DynamicEntryMetaObject : DynamicMetaObject
    {
        internal DynamicEntryMetaObject(
            Expression parameter,
            DynamicDataEntry value)
            : base(parameter, BindingRestrictions.Empty, value)
        {
        }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            var methodInfo = typeof(DataEntry).GetMethod("GetEntryValue", BindingFlags.Instance | BindingFlags.NonPublic);
            var arguments = new Expression[]
        {
            Expression.Constant(binder.Name)
        };
            Expression objectExpression = Expression.Call(
                Expression.Convert(Expression, LimitType), 
                methodInfo, arguments);

            return new DynamicMetaObject(
                objectExpression,
                BindingRestrictions.GetTypeRestriction(Expression, this.RuntimeType));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)