我正在努力解决实现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;
}
}
}
当我调试代码时,我可以看到循环中第一行的StringProperty上的GetMetaObject总是在第一次迭代时被调用,但是在下一次迭代中没有调用GetMetaObject - 而是DLR执行值的表达式[从上一次迭代开始,因此将StringProperty评估为“a”。但Assert.AreEqual调用触发GetMetaObject执行,StringProperty正确评估为“b”。
这种行为让我疯了,我无法理解可能导致它的原因。有没有人有任何想法?
UPDATE 我收到了一个建议,要从DynamicObject而不是IDynamicMetaObjectProvider派生我的类。长话短说:我知道DynamicObject,但它不适合我的情况。我只发布了一个简单的例子来说明发生了什么。真正的实现需要从另一个类派生而不是DataEntry,这样的派生是必不可少的,所以我必须实现IDynamicMetaObjectProvider,即使它更有用。
答案 0 :(得分: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));
}
}
}
答案 1 :(得分:0)
如果您想编写自己的动态对象实现,则很少需要自己实现整个界面。从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>
{
// ...
虽然为什么你还希望动态对象也实现一个接口,我仍然有点不确定。这两件事似乎相互矛盾:)
<强>更新强>
或者,如果您必须从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:
这不起作用 - 编译器过于诡计。