Cómo puedo obtener una instancia de objeto de () = > foo.Título expresión


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 ??? para la instancia de mi clase Foo. ¿Cómo obtengo una referencia a la instancia que llama foo desde la expresión lambda?

Editar: La instancia debería estar en algún lugar porque puedo llamar a property.Compile() y crear un delegado que use la instancia foo dentro de mi BindToText función. Así que mi pregunta es si esto se puede hacer sin añadir una referencia a la instancia en los parámetros de la función. Hago un llamamiento a la navaja de Occum para que dé la solución más simple.

Editar 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);.

Author: Community, 2011-02-23

4 answers

Small LINQPad muestra 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);
}
 15
Author: Julien Roncaglia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-23 16:47:53

No. 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 );

Sigue siendo agradable, pero fácil de entender. No hay magia sucediendo. ;)

 3
Author: hangy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:06

Bueno, esto es similar en tono a la solución de Hangy, pero creo que es bastante cómodo de usar y no requiere mucha magia:

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

Este es básicamente un método de extensión que se puede invocar en cualquier objeto que actúe como fuente. Le devuelve un Enlace para una propiedad de texto que puede agregar a cualquier colección de enlaces.

 1
Author: flq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-23 16:01:29

Algo como lo siguiente debería funcionar:

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

Hazme saber si eso no funciona para ti.

 1
Author: Charles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-23 16:30:01