Boucle Foreach utilisant des arbres d'expression

.net c# expression-trees lambda

Question

J'ai vu ce problème en construisant des arbres d'expression dynamiques et des arbres d' expression / énoncé . Depuis que je suis nouveau dans l'arborescence d'expression, je n'arrive toujours pas à comprendre comment atteindre ce que je veux.

Un objet artificiel est en dessous

    public class TestObject
    {
        public TestObject()
        {
            ClassList = new List<Class>();
        }
        public int Age { get; set; }
        public List<Class> ClassList { get; set; } 
    }

    public class Class
    {
        public string Name { get; set; }
        public int ClassId { get; set; }
    }

Au moment de l'exécution, j'effectue une itération sur chacune des propriétés et génère un délégué qui effectue une conversion en chaîne de cette propriété. J'ai tout ce travail. Le problème que je dois traiter maintenant est que, pour le type List, je dois pouvoir appliquer un ensemble d’actions à chaque élément de la propriété ClassList, de sorte que j’ai besoin d’un foreach qui me permette de le faire.

J'ai actuellement cette

//type==TestObject at runtime
//propertyName == "ClassList"
   ParameterExpression recordExpression = Expression.Parameter(type, "record");

   memberExpression = MemberExpression.Property(recordExpression, propertyName);

   Type getEnumerableDelegateType =
                typeof(Func<,>).MakeGenericType(new Type[] { type, memberExpression.Type}); 

   var getList = Expression.Lambda(getEnumerableDelegateType, memberExpression, recordExpression);

GetList une fois compilé et appelé renvoie la liste comme prévu. Ce qui m'est difficile, c'est de créer une expression qui utilisera le résultat de l'expression lambda et itérera dessus en appliquant l'ensemble des actions que j'ai déjà créées pour chaque élément de classe.

En fin de compte, je recherche une signature lambda qui correspond à la signature globaleAction ci-dessous.

   var getListFunc = new Func<TestObject, List<Class>>((TestObject obj1) => obj1.ClassList);

   Action<List<Class>> listAction = delegate(List<Class> data)
                {
                    foreach (var dataChannelWithUnitse in data)
                    {
                        //Apply generated delegate
                    }
                };

     Action<TestObject> overallAction = delegate(TestObject data)
                {
                    var x = getListFunc.Invoke(data);
                    listAction.Invoke(x as List<Class>);
                };

Toute aide est appréciée pour m'aider à comprendre comment faire cela.

J'ai actuellement ce qui fait exception avec la variable 'Entrée' de type 'TestObject' référencé de scope '', mais ce n'est pas défini

    var typeParam = Expression.Parameter(type, "Input");
    var listVariable = Expression.Variable(memberExpression.Type, "List");
    var enumerator = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(dataType));


    var enumeratorType = typeof(IEnumerator<>).MakeGenericType(dataType);
    var enumerableType = typeof(IEnumerable<>).MakeGenericType(dataType);
    var enumerableParam = Expression.Parameter(enumerableType, "ExtractedCollection");

    var getEnumeratorFunc = Expression.Call(enumerableParam, enumerableType.GetMethod("GetEnumerator"));
    var getEnumeratorLambda = Expression.Lambda(getEnumeratorFunc, enumerableParam);

    var t1 = Expression.Assign(listVariable, Expression.Invoke(getListLambda, typeParam));
    var t2 = Expression.Assign(enumerator, Expression.Invoke(getEnumeratorLambda, listVariable));


    var @break = Expression.Label();

    var funcBlock = Expression.Block(
        new ParameterExpression[] { listVariable, enumerator},

   t1,
   t2,

    Expression.Loop(
        Expression.IfThenElse(

            Expression.NotEqual(Expression.Call(enumerator,typeof(IEnumerator).GetMethod("MoveNext")),Expression.Constant(false)),
                                Expression.Invoke(enumerableExpressions[0],Expression.Property(enumerator, "Current")),

                      Expression.Break(@break))
            , @break), typeParam);



    Expression<Action<TestObject>> lm = Expression.Lambda<Action<TestObject>>(funcBlock,recordExpression);
    var d = lm.Compile(); **//this is exceptioning with " variable 'Input' of type 'TestObject' referenced from scope '', but it is not defined**

Réponse acceptée

Je me suis égaré quelque part au milieu de votre question (et si je l'ai mal interprétée, dites-le-moi, et j'y reviendrai), mais je pense que c'est ce que vous recherchez:

public static Expression ForEach(Expression collection, ParameterExpression loopVar, Expression loopContent)
{
    var elementType = loopVar.Type;
    var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
    var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);

    var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
    var getEnumeratorCall = Expression.Call(collection, enumerableType.GetMethod("GetEnumerator"));
    var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);

    // The MoveNext method's actually on IEnumerator, not IEnumerator<T>
    var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));

    var breakLabel = Expression.Label("LoopBreak");

    var loop = Expression.Block(new[] { enumeratorVar },
        enumeratorAssign,
        Expression.Loop(
            Expression.IfThenElse(
                Expression.Equal(moveNextCall, Expression.Constant(true)),
                Expression.Block(new[] { loopVar },
                    Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
                    loopContent
                ),
                Expression.Break(breakLabel)
            ),
        breakLabel)
    );

    return loop;
}

Pour l'utiliser, vous devez fournir une collection sur laquelle itérer, une expression à substituer dans le corps de la boucle et un ParameterExpression utilisé par l'expression du corps de la boucle, qui sera affectée à la variable de boucle à chaque itération de la boucle.

Je pense que parfois les exemples parlent plus fort que les mots ...

var collection = Expression.Parameter(typeof(List<string>), "collection");
var loopVar = Expression.Parameter(typeof(string), "loopVar");
var loopBody = Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), loopVar);
var loop = ForEach(collection, loopVar, loopBody);
var compiled = Expression.Lambda<Action<List<string>>>(loop, collection).Compile();
compiled(new List<string>() { "a", "b", "c" });

EDIT: Comme le souligne correctement Jeroem Mostert dans les commentaires, cela ne reflète pas vraiment le comportement "réel" d’une boucle foreach: cela garantirait qu’elle dispose de l’énumérateur. (Cela créerait également une nouvelle instance de la variable de boucle pour chaque itération, mais cela n'a pas de sens avec les expressions). Pour le mettre en œuvre, il suffit simplement de tourner la poignée si vous vous sentez suffisamment motivé!


Pour ceux qui regardent à la maison, j'ai une méthode similaire pour générer des boucles «pour»:

public static Expression For(ParameterExpression loopVar, Expression initValue, Expression condition, Expression increment, Expression loopContent)
{
    var initAssign = Expression.Assign(loopVar, initValue);

    var breakLabel = Expression.Label("LoopBreak");

    var loop = Expression.Block(new[] { loopVar },
        initAssign,
        Expression.Loop(
            Expression.IfThenElse(
                condition,
                Expression.Block(
                    loopContent,
                    increment
                ),
                Expression.Break(breakLabel)
            ),
        breakLabel)
    );

    return loop;
}

Ceci est équivalent à l'instruction suivante, où les pseudo-variables correspondent aux expressions de la méthode ci-dessus:

for (loopVar = initValue; condition; increment)
{
    loopContent
}

Encore une fois, loopContent, condition et increment sont des expressions qui utilisent loopVar, et loopVar est affecté à chaque itération.


Réponse populaire

Voici une version légèrement élargie de l'excellente solution de canton7 , tenant compte des remarques relatives à la suppression de l' agent recenseur:

public static Expression ForEach(Expression enumerable, ParameterExpression loopVar, Expression loopContent)
{
    var elementType = loopVar.Type;
    var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
    var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);

    var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
    var getEnumeratorCall = Expression.Call(enumerable, enumerableType.GetMethod("GetEnumerator"));
    var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
    var enumeratorDispose = Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose"));

    // The MoveNext method's actually on IEnumerator, not IEnumerator<T>
    var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));

    var breakLabel = Expression.Label("LoopBreak");

    var trueConstant = Expression.Constant(true);

    var loop =
        Expression.Loop(
            Expression.IfThenElse(
                Expression.Equal(moveNextCall, trueConstant),
                Expression.Block(
                    new[] { loopVar },
                    Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
                    loopContent),
                Expression.Break(breakLabel)),
            breakLabel);

    var tryFinally =
        Expression.TryFinally(
            loop,
            enumeratorDispose);

    var body =
        Expression.Block(
            new[] { enumeratorVar },
            enumeratorAssign,
            tryFinally);

    return body;
}


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