¿Sacar el objeto de una expresión de miembro?

c# expression-trees lambda

Pregunta

Entonces, digamos que tengo la siguiente expresión en C #:

Expression<Func<string>> expr = () => foo.Bar;

¿Cómo saco una referencia a foo?

Respuesta aceptada

Expression<Func<string>> expr = () => foo.Bar;
var me = (MemberExpression)((MemberExpression)expr.Body).Expression;
var ce = (ConstantExpression)me.Expression;
var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var value = (Foo)fieldInfo.GetValue(ce.Value);

Respuesta popular

Tuve el mismo problema, pero algo más complejo, y la respuesta de Darin Dimitrov me dio un buen comienzo. Publicaré mis resultados aquí, a pesar de que esta es una pregunta "antigua".


Caso 1: el objeto raíz es un miembro objeto

    this.textBox.Text    // where 'this' has type 'Form'

... es equivalente al siguiente árbol de expresión:

.                                    +====================+
.                                    |  MemberExpression  |
.                                    +====================+
#                                      |                |
#                          .Expression |                | .Member
#                                      v                v
.                    +------------------+              +------------+
.                    | MemberExpression |              | MemberInfo |
.                    +------------------+              +------------+
#                      |              |                 .Name = "Text"
#          .Expression |              | .Member         .MemberType = Property
#                      v              v
.   +--------------------+          +------------+
.   | ConstantExpression |          | MemberInfo |
.   +--------------------+          +------------+
#    .Value = this                   .Name = "textBox"
#    .Type  = typeof(Form)           .MemberType = Field

El único lugar en este árbol de expresiones donde realmente obtiene una referencia de objeto es de ConstantExpression : le permite obtener una referencia a this . La idea básica para obtener cualquier referencia de objeto en este árbol es la siguiente:

  1. Descienda al árbol de expresiones a lo largo de los ejes .Expression hasta que llegue a un nodo ConstantExpression .

  2. Agarra la propiedad .Value ese nodo. Esta es la referencia del objeto raíz (es decir, this en el ejemplo anterior).

  3. Usando la reflexión y los nodos MemberInfo del árbol de expresiones, obtenga referencias de objetos y trabaje de regreso "arriba" del árbol de expresiones.

Aquí hay un código que demuestra esto:

Expression expr = ...;   // <-- initially set to the expression tree's root

var memberInfos = new Stack<MemberInfo>();

// "descend" toward's the root object reference:
while (expr is MemberExpression)
{
    var memberExpr = expr as MemberExpression;
    memberInfos.Push(memberExpr.Member);
    expr = memberExpr.Expression
}

// fetch the root object reference:
var constExpr = expr as ConstantExpression;
var objReference = constExpr.Value;

// "ascend" back whence we came from and resolve object references along the way:
while (memberInfos.Count > 0)  // or some other break condition
{
    var mi = memberInfos.Pop();
    if (mi.MemberType == MemberTypes.Property)
    {
        objReference = objReference.GetType()
                                   .GetProperty(mi.Name)
                                   .GetValue(objReference, null);
    }
    else if (mi.MemberType == MemberTypes.Field)
    {
        objReference = objReference.GetType()
                                   .GetField(mi.Name)
                                   .GetValue(objReference);
    }
}

Caso 2: el objeto raíz es un miembro de clase estática

    Form.textBox.Text    // where 'textBox' is a static member of type 'Form'

... resulta en un árbol de expresión diferente. Nota a la referencia nula en la parte inferior izquierda:

.                                    +====================+
.                                    |  MemberExpression  |
.                                    +====================+
#                                      |                |
#                          .Expression |                | .Member
#                                      v                v
.                    +------------------+              +------------+
.                    | MemberExpression |              | MemberInfo |
.                    +------------------+              +------------+
#                      |              |                 .Name = "Text"
#          .Expression |              | .Member         .MemberType = Property
#                      v              v
.                     null          +------------+
.                                   | MemberInfo |
.                                   +------------+
#                                   .Name = "textBox"
#                                   .MemberType = Field
#                                   .DeclaringType = typeof(Form)

Aquí, no puede detener la fase de "descenso" esperando una ConstantExpression . En su lugar, dejas de descender cuando alcanzas una referencia nula. A continuación, recupera la referencia del objeto raíz de la siguiente manera:

var mi = memberInfos.Pop();
objReference = mi.DeclaringType
                 .GetField(member.Name, BindingFlags.Static)  // or .GetProperty!
                 .GetValue(null);

La fase de "ascenso" de allí en adelante es la misma que antes.


Ciertamente hay más casos (como los parámetros nombrados como el objeto raíz), pero espero que a estas alturas ya tengo la idea básica, así que voy a cortar aquí.



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