Как я могу получить экземпляр объекта из выражения () => foo.Title - PullRequest
26 голосов
/ 23 февраля 2011

У меня есть простой класс со свойством

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 класса.Как получить ссылку на вызывающий экземпляр foo из лямбда-выражения?

edit: Экземпляр должен быть где-то там, потому что я могу вызвать property.Compile() и создать делегат, которыйиспользует экземпляр foo внутри моей функции BindToText.Поэтому мой вопрос заключается в том, можно ли это сделать без добавления ссылки на экземпляр в параметрах функции.Я призываю Бритва Оккума найти простейшее решение.

edit 2: Многие не смогли заметить, что замыкание существует вдоступ к экземпляру 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 ]

16 голосов
/ 23 февраля 2011

Маленький 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);
}
4 голосов
/ 23 февраля 2011

Не. Просто измените метод, чтобы получить другой параметр, как описано в # 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 );

Все еще приятно, но легко понять. Там не происходит никакой магии. ;)

2 голосов
/ 23 февраля 2011

Должно работать что-то вроде следующего:

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

Дайте мне знать, если это не сработает для вас.

1 голос
/ 23 февраля 2011

Ну, это похоже на тональность решения 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);
}

Это в основном метод расширения, который может быть вызван на любомобъект выступает в качестве источника.Он возвращает вам Binding для свойства Text, которое вы можете добавить в любую коллекцию Bindings.

...