IDynamicMetaObjectProvider.GetMetaObject is not always called

I am struggling with a very strange problem around a class that implements the IDynamicMetaObjectProvider interface. According to the documentation, every time you try to perform dynamic binding in an instance of such a class, GetMetaObject is called to resolve the dynamically bound value.

But what I'm experiencing is a kind of mystery. Just look at this code:

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;
            }
        }
    }

, , GetMetaObject StringProperty , GetMetaObject - DLR [ index] , StringProperty "a". Assert.AreEqual GetMetaObject, StringProperty "b".

, , . - ?

UPDATE. DynamicObject IDynamicMetaObjectProvider. : DynamicObject, . , , . , DataEntry, , IDynamicMetaObjectProvider, .

+4
2

, , 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));
        }
    }
}
+3

, . System.Dynamic.DynamicObject , . , , DataEntry:

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

public class DynamicDataEntry : DynamicObject, IDictionary<string, object>
{
    // ...

, , . :)

UPDATE:

, DataEntry, , :

public class DynamicDataEntry : DataEntry, IDynamicMetaObjectProvider

    private readonly DynamicDataEntryImpl _inner;

    public DynamicDataEntry() {
        _inner = new DynamicDataEntryImpl(this);
    }

    // delegate to inner
    public virtual IEnumerable<string> GetDynamicMemberNames()
    {
        return _inner.GetDynamicMemberNames();
    }

    public virtual DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return _inner.GetMetaObject(parameter);
    }

    // etc...

    // delegated class
    internal class DynamicDataEntryImpl : DynamicObject {
        private readonly DataEntry _outer;

        private DyanmicDataEntryImpl(DataEntry outer) {
             _outer = outer;
        }

        // ...
    }
}

?

2:

- .

0

All Articles