如何从 ()=>foo.Title 表达式中获取对象实例

27

我有一个简单的类,其中包含一个属性。

class Foo 
{ 
    string Title { get; set; } 
}
我试图通过调用函数来简化数据绑定。
BindToText(titleTextBox, ()=>foo.Title );

这个变量的声明如下

void BindToText<T>(Control control, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;

    control.DataBindings.Add("Text", ??? , name);
}

那么我在我的Foo类实例中应该放什么?如何从lambda表达式中获取对调用的foo实例的引用?

编辑: 实例应该在某个地方,因为我可以调用property.Compile()并创建一个使用BindToText函数内部的foo实例的委托。所以我的问题是,是否可以在不将实例添加到函数参数中的情况下完成这项工作。 我依靠奥卡姆剃刀原理得出最简单的解决方案。

编辑2: 许多人没有注意到在编译lambda时访问foo实例时存在的闭包。 编译器怎么知道在哪里找到实例,而我却不知道?我坚信必须有一个答案,无需传递额外的参数。


解决方案

感谢VirtualBlackFox,解决方法如下:

void BindText<T>(TextBoxBase text, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;
    var fex = mex.Expression as MemberExpression;
    var cex = fex.Expression as ConstantExpression;            
    var fld = fex.Member as FieldInfo;
    var x = fld.GetValue(cex.Value);
    text.DataBindings.Add("Text", x, name);            
}

这使我可以简单地键入BindText(titleText, () => foo.Title);

4个回答

18

这是一个小的LINQPad示例,展示了你想要的内容:

void Foo<T>(Expression<Func<T>> prop)
{
    var propertyGetExpression = prop.Body as MemberExpression;

    // Display the property you are accessing, here "Height"
    propertyGetExpression.Member.Name.Dump();

    // "s" is replaced by a field access on a compiler-generated class from the closure
    var fieldOnClosureExpression = propertyGetExpression.Expression as MemberExpression;

    // Find the compiler-generated class
    var closureClassExpression = fieldOnClosureExpression.Expression as ConstantExpression;
    var closureClassInstance = closureClassExpression.Value;

    // Find the field value, in this case it's a reference to the "s" variable
    var closureFieldInfo = fieldOnClosureExpression.Member as FieldInfo;
    var closureFieldValue = closureFieldInfo.GetValue(closureClassInstance);

    closureFieldValue.Dump();

    // We know that the Expression is a property access so we get the PropertyInfo instance
    // And even access the value (yes compiling the expression would have been simpler :D)
    var propertyInfo = propertyGetExpression.Member as PropertyInfo;
    var propertyValue = propertyInfo.GetValue(closureFieldValue, null);
    propertyValue.Dump();
}

void Main()
{
    string s = "Hello world";
    Foo(() => s.Length);
}

1
可能是一些神奇的LINQPad方法。你可以直接返回或打印它。 - hangy
+1 让我更接近了。然而,constantInstanceExpression.Value 返回的是表单的实例,而不是表单中的字段 foo。如果表单中有几个用于绑定的类,怎么办?我不想静态地绑定到 foo,而是要绑定到属性中使用的任何内容。 - John Alexiou
已经添加了示例中可能需要的所有内容:D甚至手动解释表达式以获取字段值:D - Julien Roncaglia
[v/] 就是这样!现在我有了一种简单的数据绑定方式...太棒了。 - John Alexiou

4

不要这样做。只需要修改方法,添加另一个参数,具体请参考#3444294。对于你的示例,可能是这样的:

void BindToText<T>(Control control, T dataSource, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;

    control.DataBindings.Add("Text", dataSource, name);
}

并且将会被称为

BindToText(titleTextBox, foo, ()=>foo.Title );

依然不错,但易于理解。这里没有任何魔法发生。;)


谢谢,但我想避免这样做。请看我的上面的编辑,并且可以看到应该有一种方法来解决它,只是我不知道怎么做。 - John Alexiou

2

类似以下内容应该可以工作:

void BindToText<T>(Control control, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;

    var fooMember = mex.Expression as MemberExpression;
    var fooConstant = fooMember.Expression as ConstantExpression;
    var foo = fooConstant.Value;

    control.DataBindings.Add("Text", foo, name);
}

如果这个方法对你不起作用,请告诉我。

哎呀,刚意识到这个答案与VisualBlackFox的答案几乎相同... - Charles

1

嗯,这与Hangy的解决方案在语气上相似,但我认为它更加舒适易用,不需要太多魔法:

public static Binding CreateTextBinding<T>(this T source, Expression<Func<T,object>> access)
{
    var mex = access.Body as MemberExpression;
    string name = mex.Member.Name;
    return new Binding("Text", source, name);
}

这基本上是一个扩展方法,可以在任何充当源的对象上调用。它会为文本属性返回一个绑定,您可以将其添加到任何绑定集合中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接