To use in expression trees, convert Funcstring, bool> to bool.

c# c#-4.0 expression-trees

Question

I've got an overly complicated binary expression tree building system It takes in a string and a pair of objects (Player and World)

Each node on the tree represents an external function that takes a string, player and world, and either returns a bool (for tests) a string (for output) or void (for actions)

My problem is threefold: Firstly I need to use something like Expression.Condition or Expression.IfThenElse where the test Expression is of the form Expression<func<string, Player, World, bool>> rather than Expresson<bool> (as Expression.And would output)

Secondly I need to be sure that the memory reference for Player and World stay the same throughout - so that if one of the nodes in the tree updates something within Player, then it'll still be updated at the next node.

Finally I need to append all the strings, one to another.

If I could hard code the tree, it might end up looking something like this:

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

To further elaborate: I have an instance of SomeClass with all of its test/assign/string methods. I then go and create a list of Expression<func<string[], World, Player, bool>>, Expression<Action<string[], World, Player>> and Expression<func<string[], World, Player, string>> and start throwing them together into an expression tree. The actual ordering of what goes where I've dealt with leaving me with (for example):

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

The issue is with the Condition statements which throw a error because it cannot convert from Func to bool. I'm also unsure whether or not the parameters are getting passed in (as I've not been able to debug through)

1
2
2/4/2012 5:11:41 AM

Accepted Answer

After much dabbling with MethodInfo, I discovered that when I was writing:

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

the Expression.Lambda was adding a layer of complexity to my code by turning Func<string, World, Player, string> into Func<string, World, Player, Func<string, World, Player, string>>

The Expression.Invoke stripped away this added layer which at first confused me. With this startling revelation I updated this to:

 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))));
2
2/5/2012 10:34:31 AM

Popular Answer

I took a shot at expressing that code as an expression. This is what I came up with. I don't know if it works as intended for all cases but it compiles and seems to work in my 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);

Here's the debug view of the 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
    }
}


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow