我有一个过于复杂的二进制表达式树构建系统 它接收一个字符串和一对对象(播放器和世界)
树上的每个节点代表一个外部函数,它接受一个字符串,播放器和世界,并返回一个bool(用于测试)一个字符串(用于输出)或void(用于操作)
我的问题有三个:
首先,我需要使用类似Expression.Condition
或Expression.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。
我也不确定参数是否传入(因为我无法通过调试)
答案 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
}
}