6
votes

Réflexion pour appeler la méthode générique avec le paramètre d'expression Lambda

Je recherche un moyen d'appeler une méthode générique avec une expression Lambda que les appels contiennent dans un tableau d'articles.

Dans ce cas, j'utilise un cadre d'entité dans lequel la méthode, mais le scénario pourrait être appliqué dans d'autres inumérables. P>

Je dois appeler la dernière ligne du code ci-dessus par réflexion, de sorte que je puisse utiliser tout type et n'importe quel bien à passer à la méthode contient. p> xxx pré>

dans la recherche, j'ai remarqué que je devais utiliser getMethod, faire de la créatrice et l'expression pour atteindre cela, mais je ne pouvais pas 't comprendre comment le faire. Il serait très utile d'avoir cet exemple afin que je puisse comprendre à quel point la réflexion fonctionne avec les concepts Lambda et générique. P>

Fondamentalement, l'objectif est d'écrire une version correcte d'une fonction comme celle-ci: P>

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    return target.Where(t => searchValues.Contains(t.propertyName));
    //Known the following:
    //1) This function intentionally can't be compiled
    //2) Where function can't be called directly from an untyped IEnumerable
    //3) t is not actually recognized as a Type, so I can't access its property 
    //4) The property "propertyName" in t should be accessed via Linq.Expressions or Reflection
    //5) Contains function can't be called directly from an untyped IEnumerable
}

//Testing environment
static void Main()
{
    var listOfPerson = new List<Person> { new Person {Id = 3}, new Person {Id = 1}, new Person {Id = 5} };
    var searchIds = new int[] { 1, 2, 3, 4 };

    //Requirement: The function must not be generic like GetFilteredList<Person> or have the target parameter IEnumerable<Person>
    //because the I need to pass different IEnumerable types, not known in compile-time
    var searchResult = GetFilteredList(listOfPerson, "Id", searchIds);

    foreach (var person in searchResult)
        Console.Write(" Found {0}", ((Person) person).Id);

    //Should output Found 3 Found 1
}


0 commentaires

3 Réponses :


0
votes

Vous pouvez résoudre votre problème en utilisant l'ensemble de classes suivant.

Tout d'abord, nous devons créer une classe de contient qui décidera quels éléments seront choisis à partir du tableau source. P>

class Program
{
    static void Main()
    {
        var items = new object[] { 1, 2, 3, 4 };
        var items2 = new object[] { 2, 3, 4, 5 };

        new ToList(items, items2).Value.ForEach(x => Console.WriteLine(x));

        Console.Read();
    }
}


3 commentaires

Mario, merci pour votre temps, mais votre solution utilisant des génériques ne correspond pas à mes besoins, car je n'ai que le type et la propriété à tester en runtime. Je vais mettre à jour la question et essayer d'expliquer mieux.


J'ai mis à jour la réponse afin que vous n'ayez pas à utiliser des types génériques. Au lieu de cela, vous utiliserez des tableaux d'objet de type pour stocker vos articles.


Cela ne fonctionne que pour les types de base comme INT. Mon cas utilise des types de référence, qui nécessitent une propriété à évaluer avec contient. Par exemple, je devrais tester une liste de "personne" sur une liste d'ID INT et renvoyer toutes les "personne" qui possède l'un de ces identifiants: var arrayofids = nouveau [] {1, 2, 3, 4 }; // Cette ligne ne fonctionne pas parce que la personne n'est pas connue dans la compilation, donc je ne peux donc pas accéder à la liste de rétablissement de la propriété "ID" de la propriété de propriétéObject.where (p => arrayofids.contains (p.Id));



13
votes

Après une vaste recherche et beaucoup d'études d'expressions, je pouvais écrire une solution moi-même. Il peut certainement être amélioré, mais correspond exactement à mes besoins. Espérons que cela peut aider quelqu'un d'autre.

//Return all items from a IEnumerable(target) that has at least one matching Property(propertyName) 
//with its value contained in a IEnumerable(possibleValues)
static IEnumerable GetFilteredList(IEnumerable target, string propertyName, IEnumerable searchValues)
{
    //Get target's T 
    var targetType = target.GetType().GetGenericArguments().FirstOrDefault();
    if (targetType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "target");

    //Get searchValues's T
    var searchValuesType = searchValues.GetType().GetGenericArguments().FirstOrDefault();
    if (searchValuesType == null)
        throw new ArgumentException("Should be IEnumerable<T>", "searchValues");

    //Create a p parameter with the type T of the items in the -> target IEnumerable<T>
    var containsLambdaParameter = Expression.Parameter(targetType, "p");

    //Create a property accessor using the property name -> p.#propertyName#
    var property = Expression.Property(containsLambdaParameter, targetType, propertyName);

    //Create a constant with the -> IEnumerable<T> searchValues
    var searchValuesAsConstant = Expression.Constant(searchValues, searchValues.GetType());

    //Create a method call -> searchValues.Contains(p.Id)
    var containsBody = Expression.Call(typeof(Enumerable), "Contains", new[] { searchValuesType }, searchValuesAsConstant, property);

    //Create a lambda expression with the parameter p -> p => searchValues.Contains(p.Id)
    var containsLambda = Expression.Lambda(containsBody, containsLambdaParameter);

    //Create a constant with the -> IEnumerable<T> target
    var targetAsConstant = Expression.Constant(target, target.GetType());

    //Where(p => searchValues.Contains(p.Id))
    var whereBody = Expression.Call(typeof(Enumerable), "Where", new[] { targetType }, targetAsConstant, containsLambda);

    //target.Where(p => searchValues.Contains(p.Id))
    var whereLambda = Expression.Lambda<Func<IEnumerable>>(whereBody).Compile();

    return whereLambda.Invoke();
}


2 commentaires

Juste pour le compte rendu, une amélioration pourrait être un moyen d'appeler invoquée au lieu de dynamicinvoke dans la déclaration de retour.


2 ans plus tard, toujours le meilleur exemple de création d'expression dynamique que j'ai trouvé.Je ajoutera que c'est beaucoup plus rapide si vous modifiez le type à iquéryable plutôt que ienumerable , Comme la requête n'est pas obligée d'exécuter immédiatement du côté du client et se passe plutôt sur la source de données (par exemple, exécutée par le serveur SQL lors de l'utilisation de LINQ-TO-SQL)



5
votes

Afin d'éviter d'utiliser des génériques (car les types ne sont pas connus au moment de la conception), vous pouvez utiliser une réflexion et construire l'expression "à la main"

Vous auriez besoin de le faire en définissant une expression "contient" à l'intérieur. L'une des loisirs: xxx

dans ce cas "codigo" est codé dur, mais il pourrait s'agir d'un paramètre pour obtenir n'importe quelle propriété du type que vous définissez.

Vous pouvez le tester à l'aide de: xxx


1 commentaires

J'ai utilisé votre code (pour interrographique) pour appliquer la solution pour l'entité Framework DBSet requête, c'est-à-dire d'utiliser l'expression originale DBSet comme DBSet.aSquérissable (). Expression de la clause WHERE, utilisée par la méthode Creequy of the DBSet QueryAdraVider. De cette façon, EF générera à l'interne une clause SQL parfaite. BTW, certaines parties de votre code (E.G. makegenericMethod Line) pourraient être converties en expression pure au lieu de l'approche de réflexion. Merci!