Expresión selecta lambda dinámica

.net c# expression-trees lambda reflection

Pregunta

Tengo un problema con el árbol de expresiones lambda que no puedo resolver. Estoy tratando de hacer una declaración dinámica de linq Select.

Tengo un repositorio dinámico creado aquí:

private static dynamic GetRepository(Type type)
{
    dynamic repository = typeof(IFactory).GetMethod("Create").MakeGenericMethod(typeof(IRepository<>).MakeGenericType(type)).Invoke(ObjectFactory.Instance, new object[] { });
    return repository;
}

Con esto necesito llamar a esto solo que no sé x y SomeProperty en tiempo de compilación. Tengo PropertyInfo PropertyInfo con el nombre de SomeProperty y Type ObjectType con x type. Falla en el Objetivo 1 con esta excepción:

System.Reflection.AmbiguousMatchException at GetMethod (nombre de cadena)

El código:

private SomeObject CreateSomeObject (PropertyInfo propertyInfo, Type objectType)
{
    var param = Expression.Parameter(objectType, "x");
    MemberExpression expression = Expression.PropertyOrField(param, propertyInfo.Name);

    //Goal 1: var selectExpression = Expression.Lambda<Func<objectType, object>>(expression, param);
    var selectExpression = typeof(Expression).GetMethod("Lambda").MakeGenericMethod(typeof(Func<,>)
    .MakeGenericType(objectType, typeof(object)))
    .Invoke((object)null, new object[] { expression, param });

    // Goal 2: List<object> list = GetRepository(objectType).FindAllQuery().Select(x => x.SomeProperty).ToList();
    List<object> list = GetRepository(objectType).FindAll().Select(selectExpression);
}

¿Cómo resolver esto?

Actualización 1:

He cambiado la forma de seleccionar el método Lambda, la forma de empaquetar el parámetro 'param' y he añadido un convertidor de objetos a 'expresión'.

private SomeObject CreateSomeObject (PropertyInfo propertyInfo, Type objectType)
{
    var param = Expression.Parameter(objectType, "x");
    Expression expression = Expression.Convert(Expression.PropertyOrField(param, propertyInfo.Name), typeof(object));

    //Goal 1: var selectExpression = Expression.Lambda<Func<objectType, object>>(expression, param);
    var selectExpression = typeof(Expression).GetMethods().First(m => m.Name == "Lambda" && m.IsGenericMethod)
    .MakeGenericMethod(typeof(Func<,>)
    .MakeGenericType(objectType, typeof(object)))
    .Invoke((object)null, new object[] { expression, new [] { param }});

    // Goal 2: List<object> list = GetRepository(objectType).FindAllQuery().Select(x => x.SomeProperty).ToList();
    List<object> list = GetRepository(objectType).FindAll().Select(selectExpression);
}

Pero sé que obtengo esta excepción en el Objetivo 2 (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException):

'System.Collections.Generic.List' no contiene una definición para 'Select'

Esto es en parte correcto porque está definido en System.Linq y es un método de extensión. ¿Cómo hago para que esto funcione?

Respuesta aceptada

El código que lanza la excepción es

typeof(Expression).GetMethod("Lambda")

porque hay 18 métodos llamados Lambda definidos en el tipo de Expression (por lo tanto, la AmbiguousMatchException ).

GetMethod(string methodName) es apropiado cuando no hay sobrecargas. En este caso, usaría GetMethods() y luego filtraría el que necesito.

En su caso, la sobrecarga correcta es

Expression.Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters)

Podría escribir una función que valide la sobrecarga correcta verificando el número de parámetros y su tipo, pero encontré una alternativa más fácil: filtre el método por la representación .ToString() , que en nuestro caso es:

System.Linq.Expressions.Expression`1[TDelegate] Lambda[TDelegate](System.Linq.Expressions.Expression, System.Linq.Expressions.ParameterExpression[])

También hay un problema con la forma en que pasa los parámetros ( new object[] { expression, param } ). El segundo parámetro no es del tipo ParameterExpression , sino de ParameterExpression[] (array), por lo tanto, debe pasar un new[]{param} lugar de solo param . Al llamarlo en código normal, funciona así porque está definido como params ParameterExpression[] .

En conclusión, el siguiente código debería funcionar en su caso:

const string methodSignature = 
    "System.Linq.Expressions.Expression`1[TDelegate] Lambda[TDelegate]" +
    "(System.Linq.Expressions.Expression, System.Linq.Expressions.ParameterExpression[])";

var lambdaMethod = typeof (Expression).GetMethods()
    .Single(mi => mi.ToString() == methodSignature);

var funcType = typeof (Func<,>).MakeGenericType(objectType, typeof (object));

var genericLambda = lambdaMethod.MakeGenericMethod(funcType);

var selectExpression = genericLambda.Invoke(null, new object[] { expression, new[] { param } });


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