【问题标题】:Func<string, bool> to bool for use in expression treesFunc<string, bool> to bool 用于表达式树
【发布时间】:2012-01-31 15:51:46
【问题描述】:

我有一个过于复杂的二叉表达式树构建系统 它接受一个字符串和一对对象(玩家和世界)

树上的每个节点代表一个外部函数,它接受一个字符串、玩家和世界,并返回一个布尔值(用于测试)、一个字符串(用于输出)或 void(用于动作)

我的问题有三个: 首先,我需要使用Expression.ConditionExpression.IfThenElse 之类的东西,其中测试表达式的形式为Expression&lt;func&lt;string, Player, World, bool&gt;&gt; 而不是Expresson&lt;bool&gt;(因为Expression.And 会输出)

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

最后我需要将所有的字符串一个接一个地追加。

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

    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 的实例及其所有测试/分配/字符串方法。 然后我去创建一个Expression&lt;func&lt;string[], World, Player, bool&gt;&gt;Expression&lt;Action&lt;string[], World, Player&gt;&gt;Expression&lt;func&lt;string[], World, Player, string&gt;&gt; 的列表,并开始将它们一起放入表达式树中。 我处理过的事情的实际顺序(例如):

    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。 我也不确定参数是否被传入(因为我无法调试)

【问题讨论】:

  • 您能详细说明一下吗?你在你的问题中所说的对我来说没有意义。你有什么(请举一个具体的例子)?你想用它做什么(再次,请具体说明)?你有哪些表达方式,你打算如何使用它们?
  • 我在当前的问题实现中添加了更多细节
  • 有什么特别的原因为什么你想要所有这些作为一个表达式?我认为根本不需要它。如果您想要表达式的示例并查看它是否实际可行,您可以将函数存储为 lambda 表达式。
  • 每个都是表达式的原因是我可以将它们构建成表达式树。
  • 最终程序将有很大一部分代码作为动态生成的表达式树。这些树将由可更改的配置文件构建而成,可能很简单(如上面的那个)或更复杂。我希望将它作为二叉树,除了节点必须做的不仅仅是测试(必须是单独的表达式)并且表达式表示返回布尔值而不是仅仅返回布尔值的函数。 if 语句也可能要复杂得多。

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


【解决方案1】:

在接触了 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&lt;string, World, Player, string&gt; 转换为Func&lt;string, World, Player, Func&lt;string, World, Player, string&gt;&gt;,为我的代码增加了一层复杂性

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 对于代码,我假设SomeClass 是静态的。我想不出一个很好的方法来将它表示为一个没有在块中声明的实例变量。但您应该能够根据自己的需要对其进行调整。
    • ps,这里不要使用Expression.Condition,它对应于条件运算符?:(又名“三元”运算符),这不是您在代码中实际拥有的。
    • 我已经避免传递methodinfo,因为它限制了我可以对原始类执行的操作。使用 lambdas 而不是直接的 methodinfo 所赋予的额外灵活性需要更复杂的示例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    相关资源
    最近更新 更多