Expression de sélection lambda dynamique

.net c# expression-trees lambda reflection

Question

J'ai un problème d'arbre d'expression lambda que je n'arrive pas à résoudre. J'essaie de créer une instruction linq Select dynamique.

J'ai un référentiel dynamique créé ici:

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

Avec cela, je n'ai besoin que d'appeler ceci, je ne connais pas x et SomeProperty au moment de la compilation. J'ai PropertyInfo propertyInfo avec le nom SomeProperty et le type objectType avec le type x. Il échoue au but 1 avec cette exception:

System.Reflection.AmbiguousMatchException à GetMethod (nom de chaîne)

Le code:

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

Comment résoudre ceci?

Mise à jour 1:

J'ai changé la manière de sélectionner la méthode Lambda, la manière de compresser le paramètre 'param' et j'ai ajouté un objet Converter à 'expression'.

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

Mais sachez que cette exception correspond à l’objectif 2 (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException):

'System.Collections.Generic.List' ne contient pas de définition pour 'Select'

Ceci est en partie correct car il est défini dans System.Linq et qu'il s'agit d'une méthode d'extension. Comment puis-je obtenir ce travail?

Réponse acceptée

Le code qui lève l'exception est

typeof(Expression).GetMethod("Lambda")

car il existe 18 méthodes appelées Lambda définies sur le type Expression (d’où l’ AmbiguousMatchException ).

GetMethod(string methodName) est approprié lorsqu'il n'y a pas de surcharge. Dans ce cas, j'utiliserais GetMethods() , puis filtrerais celui dont j'avais besoin.

Dans votre cas, la surcharge correcte est

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

Vous pourriez écrire une fonction qui valide la surcharge correcte en vérifiant le nombre de paramètres et leur type, mais j'ai trouvé une alternative plus simple: filtrer la méthode à l' .ToString() représentation .ToString() , qui dans notre cas est:

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

Il y a aussi un problème avec la façon dont vous passez les paramètres ( new object[] { expression, param } ). Le second paramètre n'est pas de type ParameterExpression , mais ParameterExpression[] (array). Par conséquent, vous devez passer new[]{param} au lieu de simplement param . Quand on l'appelle en code normal, ça marche comme ça parce que c'est défini comme params ParameterExpression[] .

En conclusion, le code suivant devrait fonctionner dans votre cas:

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


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow