Func <string,bool =“”> bool用于表达式树</string,>

时间:2012-01-31 15:51:47

标签: c# c#-4.0 expression-trees

我有一个过于复杂的二进制表达式树构建系统 它接收一个字符串和一对对象(播放器和世界)

树上的每个节点代表一个外部函数,它接受一个字符串,播放器和世界,并返回一个bool(用于测试)一个字符串(用于输出)或void(用于操作)

我的问题有三个: 首先,我需要使用类似Expression.ConditionExpression.IfThenElse的内容,其中测试表达式的格式为Expression<func<string, Player, World, bool>>,而不是Expresson<bool>(因为Expression.And会输出)

其次,我需要确保Player和World的内存引用始终保持相同 - 这样,如果树中的一个节点更新了Player内的某些内容,那么它仍将在下一个节点更新。 / p>

最后,我需要将所有字符串相互追加。

如果我可以对树进行硬编码,最终可能会看起来像这样:

    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 个答案:

答案 0 :(得分:2)

经过多次涉及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))));

答案 1 :(得分:0)

我尝试将该代码表达为表达式。这就是我提出的。我不知道它是否适用于所有情况,但它编译并且似乎在我的测试中有效。

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