使用工作流来评估动态表达式

时间:2016-10-24 19:43:39

标签: c# workflow-foundation-4

我想将一个对象和表达式传递给一个动态创建的工作流,以模仿许多语言中的Eval函数。任何人都能帮我解决我做错的事吗?下面的代码是一个非常简单的示例,如果接受一个Policy对象,其溢价为1.05,然后返回结果。它引发了异常:

其他信息:处理工作流树时遇到以下错误:

'DynamicActivity':活动'1:DynamicActivity'的私有实现具有以下验证错误:未提供所需活动参数'To'的值。

代码:

System.out.println
br.readLine();

1 个答案:

答案 0 :(得分:1)

您可以使用Workflow Foundation来评估表达式,但使用几乎任何其他选项都要容易得多。

您的代码使用的关键问题是您没有尝试评估表达式(使用VisualBasicValueCSharpValue)。分配InArgument`1.Expression是尝试设置值 - 而不是将值设置为表达式的结果。

请记住,编译表达式相当慢(> 10ms),但可以缓存生成的编译表达式以便快速执行。

使用工作流程:

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页面示例。