Func <string, bool> для bool для использования в деревьях выражений - PullRequest
2 голосов
/ 31 января 2012

У меня слишком сложная система построения дерева двоичных выражений Требуется строка и пара объектов (игрок и мир)

Каждый узел дерева представляет собой внешнюю функцию, которая принимает строку, игрока и мир и возвращает либо bool (для тестов) строку (для вывода), либо void (для действий)

Моя проблема тройная: Во-первых, мне нужно использовать что-то вроде Expression.Condition или Expression.IfThenElse, где тестовое выражение имеет форму Expression<func<string, Player, World, bool>>, а не Expresson<bool> (как выдаст Expression.And)

Во-вторых, я должен быть уверен, что ссылка на память для Игрока и Мира останется неизменной повсюду - так что если один из узлов в дереве обновит что-то в Игроке, то он все равно будет обновлен на следующем узле.

Наконец, мне нужно добавить все строки, одну к другой.

Если бы я мог жестко закодировать дерево, оно могло бы выглядеть примерно так:

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

Для дальнейшей разработки: У меня есть экземпляр SomeClass со всеми его методами test / assign / string. Затем я иду и создаю список Expression<func<string[], World, Player, bool>>, Expression<Action<string[], World, Player>> и Expression<func<string[], World, Player, string>> и начинаю бросать их вместе в дерево выражений. Фактическое упорядочение того, что происходит, где я имел дело, оставляет меня (например):

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

Проблема связана с операторами Condition, которые выдают ошибку, поскольку не могут преобразовать из Func в bool. Я также не уверен, передаются ли параметры (так как я не смог отладить)

Ответы [ 2 ]

2 голосов
/ 05 февраля 2012

После долгих разговоров с MethodInfo я обнаружил, что когда я писал:

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 добавлял слой сложности в мой код, превращая Func<string, World, Player, string> в Func<string, World, Player, Func<string, World, Player, string>>

Expression.Invoke снял этот добавленный слой, который сначала смутил меня.С этим поразительным откровением я обновил это:

 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))));
0 голосов
/ 04 февраля 2012

Я попытался выразить этот код как выражение.Это то, что я придумал.Я не знаю, работает ли он должным образом для всех случаев, но он компилируется и, кажется, работает в моих тестах.

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

Вот отладочное представление выражения:

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