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

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 来评估表达式,但使用几乎任何其他选项都要容易得多。

您的代码的关键问题是您没有尝试计算表达式(使用 VisualBasicValueCSharpValue)。分配 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 页面和示例。