使用工作流评估动态表达式
Use workflow to evaluate dynamic expression
我想将对象和表达式传递到动态创建的工作流中,以模仿许多语言中的 Eval 函数。谁能帮我解决我做错的事?下面的代码是一个非常简单的示例,如果接收一个 Policy 对象,将其保费乘以 1.05,然后得到 return 结果。它抛出异常:
附加信息:处理工作流树时遇到以下错误:
'DynamicActivity':activity '1: DynamicActivity' 的私有实现有以下验证错误:未提供必需的 activity 参数 'To' 的值。
代码:
using System.Activities;
using System.Activities.Statements;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Policy p = new Policy() { Premium = 100, Year = 2016 };
var inputPolicy = new InArgument<Policy>();
var theOutput = new OutArgument<object>();
Activity dynamicWorkflow = new DynamicActivity()
{
Properties =
{
new DynamicActivityProperty
{
Name="Policy",
Type=typeof(InArgument<Policy>),
Value=inputPolicy
}
},
Implementation = () => new Sequence()
{
Activities =
{
new Assign()
{
To = theOutput,
Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }
}
}
}
};
WorkflowInvoker.Invoke(dynamicWorkflow);
}
}
public class Policy
{
public int Premium { get; set; }
public int Year { get; set; }
}
}
您可以使用 Workflow Foundation 来评估表达式,但使用几乎任何其他选项都要容易得多。
您的代码的关键问题是您没有尝试计算表达式(使用 VisualBasicValue
或 CSharpValue
)。分配 InArgument`1.Expression
是尝试设置值 - 而不是将值设置为表达式的结果。
请记住,编译表达式相当慢(>10 毫秒),但可以缓存生成的编译表达式以便快速执行。
使用工作流:
class Program
{
static void Main(string[] args)
{
// this is slow, only do this once per expression
var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");
// this is fairly fast
var policy1 = new Policy() { Premium = 100, Year = 2016 };
var result1 = evaluator.Evaluate(policy1);
var policy2 = new Policy() { Premium = 150, Year = 2016 };
var result2 = evaluator.Evaluate(policy2);
Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");
}
}
public class Policy
{
public double Premium, Year;
}
class PolicyExpressionEvaluator
{
const string
ParamName = "Policy",
ResultName = "result";
public PolicyExpressionEvaluator(string expression)
{
var paramVariable = new Variable<Policy>(ParamName);
var resultVariable = new Variable<double>(ResultName);
var daRoot = new DynamicActivity()
{
Name = "DemoExpressionActivity",
Properties =
{
new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },
new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }
},
Implementation = () => new Assign<double>()
{
To = new ArgumentReference<double>() { ArgumentName = ResultName },
Value = new InArgument<double>(new CSharpValue<double>(expression))
}
};
CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);
this.Activity = daRoot;
}
public DynamicActivity Activity { get; }
public double Evaluate(Policy p)
{
var results = WorkflowInvoker.Invoke(this.Activity,
new Dictionary<string, object>() { { ParamName, p } });
return (double)results[ResultName];
}
}
internal static class CSharpExpressionTools
{
public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)
{
// See https://docs.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions
string activityName = dynamicActivity.Name;
string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = dynamicActivity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// add assembly references
TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// attach compilation result to live activity
var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });
CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);
}
}
与等效的 Roslyn 代码比较 - 其中大部分是真正不需要的绒毛:
public class PolicyEvaluatorGlobals
{
public Policy Policy { get; }
public PolicyEvaluatorGlobals(Policy p)
{
this.Policy = p;
}
}
internal class PolicyExpressionEvaluator
{
private readonly ScriptRunner<double> EvaluateInternal;
public PolicyExpressionEvaluator(string expression)
{
var usings = new[]
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks"
};
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.ToArray();
var options = ScriptOptions.Default
.AddImports(usings)
.AddReferences(references);
this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))
.CreateDelegate();
}
internal double Evaluate(Policy policy)
{
return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;
}
}
Roslyn 有完整的文档,并且有有用的 Scripting API Samples 页面和示例。
我想将对象和表达式传递到动态创建的工作流中,以模仿许多语言中的 Eval 函数。谁能帮我解决我做错的事?下面的代码是一个非常简单的示例,如果接收一个 Policy 对象,将其保费乘以 1.05,然后得到 return 结果。它抛出异常:
附加信息:处理工作流树时遇到以下错误:
'DynamicActivity':activity '1: DynamicActivity' 的私有实现有以下验证错误:未提供必需的 activity 参数 'To' 的值。
代码:
using System.Activities;
using System.Activities.Statements;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Policy p = new Policy() { Premium = 100, Year = 2016 };
var inputPolicy = new InArgument<Policy>();
var theOutput = new OutArgument<object>();
Activity dynamicWorkflow = new DynamicActivity()
{
Properties =
{
new DynamicActivityProperty
{
Name="Policy",
Type=typeof(InArgument<Policy>),
Value=inputPolicy
}
},
Implementation = () => new Sequence()
{
Activities =
{
new Assign()
{
To = theOutput,
Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }
}
}
}
};
WorkflowInvoker.Invoke(dynamicWorkflow);
}
}
public class Policy
{
public int Premium { get; set; }
public int Year { get; set; }
}
}
您可以使用 Workflow Foundation 来评估表达式,但使用几乎任何其他选项都要容易得多。
您的代码的关键问题是您没有尝试计算表达式(使用 VisualBasicValue
或 CSharpValue
)。分配 InArgument`1.Expression
是尝试设置值 - 而不是将值设置为表达式的结果。
请记住,编译表达式相当慢(>10 毫秒),但可以缓存生成的编译表达式以便快速执行。
使用工作流:
class Program
{
static void Main(string[] args)
{
// this is slow, only do this once per expression
var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");
// this is fairly fast
var policy1 = new Policy() { Premium = 100, Year = 2016 };
var result1 = evaluator.Evaluate(policy1);
var policy2 = new Policy() { Premium = 150, Year = 2016 };
var result2 = evaluator.Evaluate(policy2);
Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");
}
}
public class Policy
{
public double Premium, Year;
}
class PolicyExpressionEvaluator
{
const string
ParamName = "Policy",
ResultName = "result";
public PolicyExpressionEvaluator(string expression)
{
var paramVariable = new Variable<Policy>(ParamName);
var resultVariable = new Variable<double>(ResultName);
var daRoot = new DynamicActivity()
{
Name = "DemoExpressionActivity",
Properties =
{
new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },
new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }
},
Implementation = () => new Assign<double>()
{
To = new ArgumentReference<double>() { ArgumentName = ResultName },
Value = new InArgument<double>(new CSharpValue<double>(expression))
}
};
CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);
this.Activity = daRoot;
}
public DynamicActivity Activity { get; }
public double Evaluate(Policy p)
{
var results = WorkflowInvoker.Invoke(this.Activity,
new Dictionary<string, object>() { { ParamName, p } });
return (double)results[ResultName];
}
}
internal static class CSharpExpressionTools
{
public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)
{
// See https://docs.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions
string activityName = dynamicActivity.Name;
string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = dynamicActivity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// add assembly references
TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// attach compilation result to live activity
var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });
CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);
}
}
与等效的 Roslyn 代码比较 - 其中大部分是真正不需要的绒毛:
public class PolicyEvaluatorGlobals
{
public Policy Policy { get; }
public PolicyEvaluatorGlobals(Policy p)
{
this.Policy = p;
}
}
internal class PolicyExpressionEvaluator
{
private readonly ScriptRunner<double> EvaluateInternal;
public PolicyExpressionEvaluator(string expression)
{
var usings = new[]
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks"
};
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.ToArray();
var options = ScriptOptions.Default
.AddImports(usings)
.AddReferences(references);
this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))
.CreateDelegate();
}
internal double Evaluate(Policy policy)
{
return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;
}
}
Roslyn 有完整的文档,并且有有用的 Scripting API Samples 页面和示例。