¿Cómo puedo obtener una instancia de objeto desde () => foo.Title expresión

.net-3.5 c# data-binding expression-trees lambda

Pregunta

Tengo una clase simple con una propiedad.

class Foo 
{ 
    string Title { get; set; } 
}

Estoy tratando de simplificar el enlace de datos llamando a una función como

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

que se declara como

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

Entonces, ¿qué pongo en ??? por ejemplo de mi clase de Foo . ¿Cómo obtengo una referencia a la instancia foo llamada desde la expresión lambda?

edit: la instancia debería estar allí en algún lugar porque puedo llamar a property.Compile() y crear un delegado que use la instancia foo dentro de mi función BindToText . Entonces, mi pregunta es si esto se puede hacer sin agregar una referencia a la instancia en los parámetros de la función. Le pido a Occum's Razor que me brinde la solución más simple.

edit 2: Lo que muchos no han notado es el cierre que existe al acceder a la instancia de foo dentro de mi función, si compilo la lambda. ¿Cómo es que el compilador sabe dónde encontrar la instancia, y yo no? Insisto en que tiene que haber una respuesta, sin tener que pasar un argumento extra.


Solución

Gracias a VirtualBlackFox la solución es tal:

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

lo que me permite simplemente escribir BindText(titleText, () => foo.Title); .

Respuesta aceptada

Pequeña muestra de LINQPad de lo que quieres:

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

Respuesta popular

No lo hagas Simplemente modifique el método para tomar otro parámetro, como se describe en # 3444294 . Para su ejemplo, puede ser algo como esto:

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

y se llamaría como

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

Todavía agradable, pero fácil de entender. No hay magia sucediendo. ;)



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow