6

I am struggling with a very strange problem around a class that implements IDynamicMetaObjectProvider interface. According to the documentation, each time there is an attempt to do a dynamic binding on an instance of such class, GetMetaObject is called to resolve the dynamically bound value.

But what I am 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;
            }
        }
    }

When I debug the code, I can see that GetMetaObject on StringProperty for the first line in the loop is always called for the first iteration, but on the next iteration GetMetaObject is not called - instead DLR executes an expression for the values[index] from the previous iteration, thus evaluating StringProperty as "a". But Assert.AreEqual call triggers GetMetaObject execution and StringProperty is correctly evaluated as "b".

This behavior is driving me nuts, I can't understand what may cause it. Does anyone have any ideas?

UPDATE I received a suggestion to derive my class from DynamicObject instead of IDynamicMetaObjectProvider. To make the long story short: I am aware of DynamicObject but it's not suitable in my case. I only posted a simple example to illustrate what's going on. The real implementation needs to be derived from another class than DataEntry, and such derivation is essential, so I have to implement IDynamicMetaObjectProvider even though it's more work.

Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100
  • I will extract and post the complete code soon. – Vagif Abilov Dec 23 '13 at 15:10
  • @BartoszKP, I have posted full implementation. – Vagif Abilov Dec 23 '13 at 15:25
  • Thanks, @BartoszKP, I have rewritten the test and replace Console.WriteLine with additional Assert, so it should fail now. Please let me know if it fails for you - if fails for me. – Vagif Abilov Dec 23 '13 at 15:41
  • Yes, now it fails. Not sure how to fix it though. – BartoszKP Dec 23 '13 at 16:08
  • Old question, but Expression.Constant(this) is really evil here -> you can't embed a non primitive or string in the il as a constant, so this actually makes the compiled delegate close over the the 'this' object and store in in the delegate. I think this is why you get this behaviour. – Nick Aug 12 '19 at 12:54

2 Answers2

5

I've found a workaround for the problem by rewriting an expression used in BindGetMember. Below is the code that works.

The important different is that the previous code used Expression.Constant(this) to refer to an instance of the class derived from DynamicMetaObject. I've found samples that used rather cryptic expression Expression.Convert(Expression, LimitType) instead. The updated version works fine with all tests.

I must say that the stuff around IDynamicMetaObjectProvider is poorly (or non-) documented, and I still have no explanation why my original code didn't work in combination with traversing IEnumerable. Thanks to some blog posts, I managed to rewrite it in a way functioning in all my scenarios.

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));
        }
    }
}
Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100
0

If you want to write your own dynamic object implementations, you rarely need to implement the entire interface yourself. It is much easier to derive from System.Dynamic.DynamicObject and override the dispatcher method you need. So, with that in mind, use an interface for your DataEntry class instead:

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

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

Although I'm still a bit iffy on why you would want a dynamic object to also implement an interface. The two things seem at odds with each other :)

UPDATE:

Alternatively, if you must derive from DataEntry, then perhaps this pattern will help:

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

        // ...
    }
}

Make sense?

update 2:

This won't work -- too much trickery for the compiler.

x0n
  • 51,312
  • 7
  • 89
  • 111
  • Thanks for the suggestion @x0n, but I can't use DynamicObject because my class needs to inherit from other base class. I posted a simplified version of the implementation, but the real one is derived from another class that is essential. So let's not go into that part, it has nothing to do with the current problem. I just want to find out why the implementation of IDynamicMetaObjectProvider behave ackward. – Vagif Abilov Dec 23 '13 at 15:45
  • Ok, no problem Vagif. Again, I'm evading your current problem (but sometimes you just need to get stuff done) but another approach would be to use a composition/delegation pattern. Continue to inherit from DataEntry but create a derivation of DynamicObject and keep a private reference to it and delegate calls to it. Then override TryGetMember as needed. – x0n Dec 23 '13 at 15:52
  • To shed some light on why it's not possible in my case: the base class lies in a different assembly that is a portable class library and doesn't use dynamic C# (so it will work for iOS). The derived class adds dynamic behavior to it by implementing IDynamicMetaObjectProvider. So there are essential reasons to split the classes in two, so IDynamicMetaObjectProvider is a perfect fit for such design requirement. I hope it clarifies why it was designed in such way. But thanks for the attention! – Vagif Abilov Dec 23 '13 at 15:57
  • Ok, but that shouldn't prevent you delegating to a DynamicObject derived instance :) If you want to avoid referencing DynamicDataEntryImpl, use IDynamicMetaObjectProvider for the field type and pass in an instance via the ctor, or use a factory. – x0n Dec 23 '13 at 16:00
  • Thanks, that's worth having a look as a workaround. Unfortunately it GetMetaObject in the suggested implementation causes an infinite recursion (_inner.GetMetaObject calls DynamicDataEntry.GetMetaObject that calls it again etc. – Vagif Abilov Dec 23 '13 at 16:14
  • Sorry -- hehe, I wrote it up in the editor - no visual studio on my RT device. You get the idea though :) I don't see the recursion issue though unless you are trying to call the outer from the inner - I only offered that ctor in case you need access to something belonging on the DataEntry base; if you tried to call the IDynamicMetaObjectProvider i/f on outer, yes that would be recursive - but why would you do that? :) – x0n Dec 23 '13 at 16:16
  • No I am not trying to do anything special: I more or less pasted your code, fixed misspelling and completed the implementation. But public virtual DynamicMetaObject GetMetaObject(Expression parameter) implementation hangs in endless recursion. – Vagif Abilov Dec 23 '13 at 16:26
  • This puzzles me if composition can be applied to this scenario. Have you tried this before, i.e. that an object derives from IDynamicMetaObjectProvider and delegates its implementation to an inner DynamicObject? Doesn't look like it works. – Vagif Abilov Dec 23 '13 at 16:28
  • No, I have not tried this before... but it should work, right? Now I'm curious. Time to find my laptop... – x0n Dec 23 '13 at 16:32
  • 1
    It might. But currently it doesn't :-) – Vagif Abilov Dec 23 '13 at 16:35
  • Ah, I see what's happening - the call to GetMetaObject on _inner is treated as a dynamic call site... – x0n Dec 23 '13 at 18:57
  • But I am event more puzzled why the straightforward IDynamicMetaObjectProvider implementation (without composition) behaves so weird. – Vagif Abilov Dec 23 '13 at 19:48