Funk zur Verwendung in Expressionsbäumen boolen

c# c#-4.0 expression-trees

Frage

Ich habe ein sehr kompliziertes binäres Expression Tree Building System. Es nimmt eine Zeichenfolge und ein Paar Objekte (Player und World) auf.

Jeder Knoten im Baum stellt eine externe Funktion dar, die eine Zeichenfolge, einen Player und eine Welt verwendet und entweder einen Bool (für Tests), eine Zeichenfolge (für die Ausgabe) oder eine ungültige (für Aktionen) zurückgibt.

Mein Problem ist dreifach: Erstens muss ich etwas wie Expression.Condition oder Expression.IfThenElse wobei der Test Ausdruck die Form Expression<func<string, Player, World, bool>> anstelle von Expresson<bool> (als Expression.And würde ausgeben)

Zweitens muss ich sicher sein, dass die Speicherreferenz für Player und World durchgehend gleich bleibt. Wenn also einer der Knoten in der Struktur etwas innerhalb von Player aktualisiert, wird er immer noch am nächsten Knoten aktualisiert.

Schließlich muss ich alle Zeichenfolgen aneinanderfügen.

Wenn ich den Baum fest codieren könnte, sieht es vielleicht so aus:

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

Um es weiter auszuarbeiten: Ich habe eine Instanz von SomeClass mit all ihren test / assign / string-Methoden. Ich gehe dann und erstelle eine Liste von Expression<func<string[], World, Player, bool>> , Expression<Action<string[], World, Player>> und Expression<func<string[], World, Player, string>> und fange an, sie in einen Ausdrucksbaum zu werfen. Die tatsächliche Reihenfolge von dem, was mich betrifft, mit dem ich mich beschäftige (zum Beispiel):

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

Das Problem ist mit den Condition Anweisungen, die einen Fehler werfen, weil es nicht von Func in bool konvertieren kann. Ich bin mir auch nicht sicher, ob die Parameter übergeben werden (da ich nicht in der Lage war, durch zu debuggen)

Akzeptierte Antwort

Nachdem ich mich mit MethodInfo beschäftigt hatte, entdeckte ich Folgendes:

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

Die Expression.Lambda fügte meinem Code eine Komplexitätsebene hinzu, indem Func<string, World, Player, string> in Func<string, World, Player, Func<string, World, Player, string>>

Die Expression.Invoke diese zusätzliche Schicht entfernt, die mich zunächst verwirrt hat. Mit dieser überraschenden Offenbarung habe ich das aktualisiert auf:

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

Beliebte Antwort

Ich habe versucht, diesen Code als Ausdruck auszudrücken. Das ist, was mir eingefallen ist. Ich weiß nicht, ob es für alle Fälle wie vorgesehen funktioniert, aber es kompiliert und scheint in meinen Tests zu funktionieren.

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

Hier ist die Debug-Ansicht des Ausdrucks:

.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
    }
}


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum