Comment construire un arbre d'expression pour Contains

c# expression-trees linq

Question

Je suis cette réponse SO pour convertir les expressions lambda en syntaxe SQL partielle.

Cependant, j'ai des problèmes pour analyser l'expression de Contains . J'ai ajouté une méthode:

private bool ParseContainsExpression(MethodCallExpression expression)
{
    MemberExpression member = (MemberExpression)expression.Arguments[0];

    var methodInfo = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });

    //TODO check if list contains value

    return false;
}

Comme je suis totalement nouveau dans les expressions, je ne sais pas d'où provient le nom et la valeur de la propriété, ainsi que la liste contenant les valeurs que je souhaite vérifier. Où ces propriétés et valeurs sont-elles stockées dans l'expression?

Réponse acceptée

En principe, vous devez traiter les propriétés Method , Object ( expression qui représente l'instance pour les appels de méthode d'instance ou null pour les appels de méthodes statiques ) et Arguments ( une collection d'expressions représentant les arguments de la méthode appelée ) de la classe MethodCallExpression .

Spécifiquement pour Contains , vous devez éviter (ou traiter différemment si nécessaire) la méthode string.Contains , mais également gérer static méthodes static comme Enumerable.Contains , ainsi que des méthodes d'instance comme ICollection<T>.Contains , List<T>.Contains etc. Pour obtenir les valeurs de la liste (lorsque cela est possible), vous devez trouver une sorte d’expression constante.

Voici un échantillon:

private bool ParseContainsExpression(MethodCallExpression expression)
{
    // The method must be called Contains and must return bool
    if (expression.Method.Name != "Contains" || expression.Method.ReturnType != typeof(bool)) return false;
    var list = expression.Object;
    Expression operand;
    if (list == null)
    {
        // Static method
        // Must be Enumerable.Contains(source, item)
        if (expression.Method.DeclaringType != typeof(Enumerable) || expression.Arguments.Count != 2) return false;
        list = expression.Arguments[0];
        operand = expression.Arguments[1];
    }
    else
    {
        // Instance method
        // Exclude string.Contains
        if (list.Type == typeof(string)) return false;
        // Must have a single argument
        if (expression.Arguments.Count != 1) return false;
        operand = expression.Arguments[0];
        // The list must be IEnumerable<operand.Type>
        if (!typeof(IEnumerable<>).MakeGenericType(operand.Type).IsAssignableFrom(list.Type)) return false;
    }
    // Try getting the list items
    object listValue;
    if (list.NodeType == ExpressionType.Constant)
        // from constant value
        listValue = ((ConstantExpression)list).Value;
    else
    {
        // from constant value property/field
        var listMember = list as MemberExpression;
        if (listMember == null) return false;
        var listOwner = listMember.Expression as ConstantExpression;
        if (listOwner == null) return false;
        var listProperty = listMember.Member as PropertyInfo;
        listValue = listProperty != null ? listProperty.GetValue(listOwner.Value) : ((FieldInfo)listMember.Member).GetValue(listOwner.Value);
    }
    var listItems = listValue as System.Collections.IEnumerable;
    if (listItems == null) return false;

    // Do whatever you like with listItems

    return true;
}

Réponse populaire

Votre implémentation est assez différente de l'exemple de réponse. Vous devez vraiment hériter d' ExpressionVisitor pour pouvoir analyser correctement l'arborescence.

Prenons cette expression pour un exemple:

var myList = new List<string> { "A" };
Expression<Func<string, bool>> a = (s) => myList.Contains(s);
ParseContainsExpression(a.Body as MethodCallExpression);

private bool ParseContainsExpression(MethodCallExpression expression)
{
    expression.Object; //myList
    expression.Arguments[0]; //s    
    return false;
}

Notez cependant que ce sont toujours des expressions , ce ne sont pas encore des valeurs réelles. Vous devez appeler l'expression pour obtenir les valeurs.

Cependant, dans notre cas, myList est en fait une ConstantExpression . Donc on peut faire ça:

((expression.Object as MemberExpression).Expression as ConstantExpression).Value; //myList

Ce qui nous renvoie la liste originale. Notez qu'il s'agit d'un accès à un champ , car l'expression est compilée dans une fermeture, ce qui place myList tant que champ dans la classe de fermeture. Comme vous pouvez le constater, nous devons faire beaucoup d’hypothèses quant au type d’expression que nous gérons (qu’il s’agisse d’un accès à un champ, puis d’une expression constante). Vous avez vraiment besoin d'une implémentation de visiteur à part entière pour le faire correctement (ce que la réponse liée décrit)



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi