Func bool pour une utilisation dans les arbres d'expression

c# c#-4.0 expression-trees

Question

J'ai un système de construction d'arbre d'expression binaire trop compliqué. Il prend une chaîne et une paire d'objets (Player et World)

Chaque nœud de l’arbre représente une fonction externe qui prend une chaîne, un joueur et un monde, et retourne soit un bool (pour les tests), une chaîne (pour la sortie) ou void (pour les actions).

Mon problème est triple: Premièrement, je dois utiliser quelque chose comme Expression.Condition ou Expression.IfThenElse où le test Expression est de la forme Expression<func<string, Player, World, bool>> plutôt que Expresson<bool> (comme Expression.And serait sortie)

Deuxièmement, je dois être sûr que la référence mémoire de Player et de World reste inchangée. Ainsi, si l'un des nœuds de l'arborescence met à jour quelque chose dans Player, il sera toujours mis à jour au nœud suivant.

Enfin, je dois ajouter toutes les chaînes les unes aux autres.

Si je pouvais coder durement l’arbre, il pourrait ressembler à ceci:

    class Main
    {
        string Foo(string text, World world, Player player)
        {
            string output;
            output += SomeClass.PrintStarting();
            if (SomeClass.Exists(text, world, player))
            {
                output += SomeClass.PrintName(text, world, player);
                SomeClass.KillPlayer(text, world, player);
                if (SomeClass.Exists(text, world, player))
                    output += SomeClass.PrintSurvived(text, world, player);
            }
            else
                output += SomeClass.PrintNotExists(text, world, player);
            return output;
        }
    }
    public class SomeClass
    {
        string PrintStart(string text, World world, Player player)
        {
            return "Starting.\n";
        }

        bool Exists(string text, World world, Player player)
        {
            player.Lives;
        }

        string PrintName(string text, World world, Player player)
        {
            return player.Name + ".\n";
        }

        string PrintSurvived(string text, World world, Player player)
        {
            return player.Name + "died.\n";
        }

        string PrintNotExists(string text, World world, Player player)
        {
            return "This person does not exist.\n";
        }

        void KillPlayer(string text, World world, Player player)
        {
            if (text != "kidding")
                player.Lives = false;
        }
    }

Pour élaborer davantage: J'ai une instance de SomeClass avec toutes ses méthodes test / assign / string. Je vais ensuite créer une liste d' Expression<func<string[], World, Player, bool>> , Expression<Action<string[], World, Player>> et Expression<func<string[], World, Player, string>> et commencez à les jeter ensemble dans un arbre d'expression. La commande réelle de ce qui se passe là où j'ai eu affaire avec moi (par exemple):

    public string Foo2(string text, World world, Player player)
    {
        ParameterExpression result = Expression.Parameter(typeof(string), "result");
        ParameterExpression inputString = Expression.Parameter(typeof(string[]), "inputString");
        ParameterExpression inputWorld = Expression.Parameter(typeof(World), "inputWorld");
        ParameterExpression inputPlayer = Expression.Parameter(typeof(Player), "inputPlayer");
        System.Reflection.MethodInfo methodInfo = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) });

        Expression textPrintStarting = (Expression<Func<string, World, Player, string>>)((Text, World, Player) => SomeClass.PrintStarting(Text, World, Player));
        Expression testExists = (Expression<Func<string, World, Player, bool>>)((Text, World, Player) => SomeClass.Exists(Text, World, Player));
        Expression textPrintName = (Expression<Func<string, World, Player, string>>)((Text, World, Player) => SomeClass.PrintName(Text, World, Player));
        Expression killPlayer = (Expression<Action<string, World, Player>>)((Text, World, Player) => SomeClass.KillPlayer(Text, World, Player));
        Expression textPrintSurvived = (Expression<Func<string, World, Player, string>>)((Text, World, Player) => SomeClass.PrintSurvived(Text, World, Player));
        Expression textPrintNotExist = (Expression<Func<string, World, Player, string>>)((Text, World, Player) => SomeClass.PrintNotExists(Text, World, Player));


        Expression innerTest =
            Expression.Condition(
                Expression.Invoke(Expression.Lambda<Func<string, World, Player, bool>>(testExists, inputString, inputWorld, inputPlayer)),
                Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintSurvived, inputString, inputWorld, inputPlayer))),
                Expression.Empty());

        Expression success = 
            Expression.Block(
                Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintName, inputString, inputWorld, inputPlayer))),
                Expression.Lambda<Action<string, World, Player>>(killPlayer, inputString, inputWorld, inputPlayer),
                innerTest);

        Expression failure =
            Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintNotExist, inputString, inputWorld, inputPlayer)));

        Expression outerTest = 
            Expression.Condition(
                Expression.Invoke(Expression.Lambda<Func<string, World, Player, bool>>(testExists, inputString, inputWorld, inputPlayer)),
                success,
                failure);

        Expression finalExpression =
            Expression.Block(
                Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintStarting, inputString, inputWorld, inputPlayer))),
                outerTest);

        return Expression.Lambda<Func<string, World, Player, string>>(
                Expression.Block(new[] { result }, 
                finalExpression)).Compile()(text, world, player);
    }

Le problème concerne les instructions Condition qui génèrent une erreur car elles ne peuvent pas convertir Func en bool. Je ne sais pas non plus si les paramètres sont transmis (car je n'ai pas pu déboguer)

Réponse acceptée

Après avoir beaucoup manipulé MethodInfo, j'ai découvert que lorsque j'écrivais:

Expression innerTest =
    Expression.Condition(
        Expression.Invoke(Expression.Lambda<Func<string, World, Player, bool>>(testExists, inputString, inputWorld, inputPlayer)),
        Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintSurvived, inputString, inputWorld, inputPlayer))),
        Expression.Empty());

Expression.Lambda ajoutait une couche de complexité à mon code en transformant Func<string, World, Player, string> en Func<string, World, Player, Func<string, World, Player, string>>

The Expression.Invoke enlevé cette couche supplémentaire qui, au début, m'a dérouté. Avec cette révélation surprenante, j'ai mis cela à jour pour:

 Expression innerTest =
        Expression.IfThen(
            Expression.Invoke(testExists, inputString, inputWorld, inputPlayer),
            Expression.Assign(result, Expression.Call(methodInfo, result, Expression.Lambda<Func<string, World, Player, string>>(textPrintSurvived, inputString, inputWorld, inputPlayer))));

Réponse populaire

J'ai tenté de traduire ce code en tant qu'expression. C'est ce que je suis venu avec. Je ne sais pas si cela fonctionne comme prévu pour tous les cas, mais il compile et semble fonctionner dans mes tests.

// reference method
static string Foo(string text, World world, Player player)
{
    string output = SomeClass.PrintStarting();
    if (SomeClass.Exists(text, world, player))
    {
        output += SomeClass.PrintName(text, world, player);
        SomeClass.KillPlayer(text, world, player);
        if (SomeClass.Exists(text, world, player))
            output += SomeClass.PrintSurvived(text, world, player);
    }
    else
        output += SomeClass.PrintNotExists(text, world, player);
    return output;
}
// helper method
static Expression AddAssignStrings(ParameterExpression left, Expression right)
{
    var stringType = typeof(string);
    var concatMethod = stringType.GetMethod("Concat", new[] { stringType, stringType });
    return Expression.Assign(
        left,
        Expression.Call(concatMethod, left, right)
    );
}

var text = Expression.Parameter(typeof(string), "text");
var world = Expression.Parameter(typeof(World), "world");
var player = Expression.Parameter(typeof(Player), "player");
var output = Expression.Variable(typeof(string), "output");

// looks safe to reuse this array for the expressions
var arguments = new ParameterExpression[] { text, world, player };

var someClassType = typeof(SomeClass);
// assuming the methods are all publicly accessible
var printStartingMethod = someClassType.GetMethod("PrintStarting");
var existsMethod = someClassType.GetMethod("Exists");
var printNameMethod = someClassType.GetMethod("PrintName");
var killPlayerMethod = someClassType.GetMethod("KillPlayer");
var printSurvivedMethod = someClassType.GetMethod("PrintSurvived");
var printNotExistsMethod = someClassType.GetMethod("PrintNotExists");

var ifTrueBlockContents = new Expression[]
{
    AddAssignStrings(output, Expression.Call(printNameMethod, arguments)),

    Expression.Call(killPlayerMethod, arguments),

    Expression.IfThen(
        Expression.Call(existsMethod, arguments),
        AddAssignStrings(output, Expression.Call(printSurvivedMethod, arguments))
    ),
};

var blockContents = new Expression[]
{
    Expression.Assign(output, Expression.Call(printStartingMethod)),

    Expression.IfThenElse(
        Expression.Call(existsMethod, arguments),
        Expression.Block(ifTrueBlockContents),
        AddAssignStrings(output, Expression.Call(printNotExistsMethod, arguments))
    ),

    output,
};

var body = Expression.Block(typeof(string), new ParameterExpression[] { output }, blockContents);

var lambda = Expression.Lambda<Func<string, World, Player, string>>(body, arguments);

Voici la vue de débogage de l'expression:

.Lambda #Lambda1<System.Func`4[System.String,Test.World,Test.Player,System.String]>(
    System.String $text,
    Test.World $world,
    Test.Player $player) {
    .Block(System.String $output) {
        $output = .Call Test.SomeClass.PrintStarting();
        .If (
            .Call Test.SomeClass.Exists(
                $text,
                $world,
                $player)
        ) {
            .Block() {
                $output = .Call System.String.Concat(
                    $output,
                    .Call Test.SomeClass.PrintName(
                        $text,
                        $world,
                        $player));
                .Call Test.SomeClass.KillPlayer(
                    $text,
                    $world,
                    $player);
                .If (
                    .Call Test.SomeClass.Exists(
                        $text,
                        $world,
                        $player)
                ) {
                    $output = .Call System.String.Concat(
                        $output,
                        .Call Test.SomeClass.PrintSurvived(
                            $text,
                            $world,
                            $player))
                } .Else {
                    .Default(System.Void)
                }
            }
        } .Else {
            $output = .Call System.String.Concat(
                $output,
                .Call Test.SomeClass.PrintNotExists(
                    $text,
                    $world,
                    $player))
        };
        $output
    }
}



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