C# WF4:如何计算仅在运行时已知的表达式?

C# WF4:如何计算仅在运行时已知的表达式?,c#,.net,workflow-foundation-4,C#,.net,Workflow Foundation 4,我试图创建一个简单的WF4活动,它接受一个包含VB.NET表达式的字符串(比如说数据库),使用工作流当前范围中可用的变量计算该字符串并返回结果。不幸的是,以我尝试过的方式,无论是简单的活动还是成熟的本地活动,我总是碰壁 我的第一次尝试是使用一个简单的活动,我能够创建一个简单的类,该类计算给定某个对象作为其输入的表达式: public class Eval<T, TResult> : Activity<TResult> { [RequiredArgument]

我试图创建一个简单的WF4活动,它接受一个包含VB.NET表达式的字符串(比如说数据库),使用工作流当前范围中可用的变量计算该字符串并返回结果。不幸的是,以我尝试过的方式,无论是简单的
活动
还是成熟的
本地活动
,我总是碰壁

我的第一次尝试是使用一个简单的活动,我能够创建一个简单的类,该类计算给定某个对象作为其输入的表达式:

public class Eval<T, TResult> : Activity<TResult>
{
    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    public Eval(string predicate)
    {
        this.Implementation = () => new Assign<TResult>
        {
            Value = new InArgument<TResult>(new VisualBasicValue<TResult>(predicate)),
            To = new ArgumentReference<TResult>("Result")
        };
    }

    public TResult EvalWith(T value)
    {
        return WorkflowInvoker.Invoke(this, new Dictionary<string, object>{ {"Value", value } });
    }
}
我尝试使用以下命令运行
NativeEval

WorkflowInvoker.Invoke(new NativeEval<int, int>(), new Dictionary<string, object>
    { { "ExpressionText", "Value + 2" }, { "Value", 5 } });

现在,即使这些行的表达式是静态的,我仍然会得到相同的错误


EDIT2:解决了我遇到的异常。我必须将变量和子活动都更改为“实现”,因此:

metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);
改为:

metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
使所有的例外都消失了。不幸的是,它揭示了以下行完全没有任何作用:

Predicate.ExpressionText = ExpressionText.Get(context);
在运行时更改
VisualBasicValue
ExpressionText
属性无效。使用ILSpy进行快速检查可以发现原因-只有在调用
CacheMetadata()
时,表达式文本才会被计算并转换为表达式树,此时表达式还不知道,这就是为什么我使用无参数构造函数来初始化表达式并将其具体化为no-op。我甚至尝试将得到的
NativeActivityMetadata
对象保存在我自己的CacheMatadata重写方法中,然后使用反射强制调用
VisualBasicValue
CacheMatadata()
,但这只是抛出了一个不同的神秘异常(“找到了不明确的匹配。”类型为AmbiguousMatchException)


此时,似乎不可能将动态表达式完全集成到工作流中,从而向其公开所有范围内的变量。我想我将在
NativeEval
类中使用
Eval
类中使用的方法。

我建议为此使用不同的框架。一个好的方法是使用nCalc

它可以解析任何表达式并计算结果,包括静态或动态参数和自定义函数。


我们使用它在运行时对不同类型的表达式求值。

如果您的“谓词”是一个众所周知的字符串,并且不需要是在运行时求值的表达式,那么您当然可以这样做,扔掉不确定项并避免构造函数:

public class Eval<T, TResult> : Activity<TResult>
{
    public string Expression { get; set; }

    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    protected override Func<Activity> Implementation
    {
        get
        {
            if (string.IsNullOrEmpty(Expression))
            {
                return base.Implementation;
            }

            return () => new Assign<TResult>
            {
                Value = new InArgument<TResult>(new VisualBasicValue<TResult>(Expression)),
                To = new ArgumentReference<TResult>("Result")
            };
        }
        set
        {
            throw new NotSupportedException();
        }
    }
}

当使用
Execute()
方法时,更改子实现没有任何效果。执行模式已启用,无法更改子树

我最后使用了以下活动。它不能访问工作流的变量,而是接受一个参数“Value”,该参数可以在动态表达式中由相同的名称使用。除此之外,它工作得相当好

public class Evaluate<TIn, TOut> : NativeActivity<TOut>
{
    [RequiredArgument]
    public InArgument<string> ExpressionText { get; set; }

    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        var result = new ExpressionEvaluator<TIn, TOut>(ExpressionText.Get(context)).EvalWith(Value.Get(context));
        Result.Set(context, result);
    }
}

public class ExpressionEvaluator<TIn, TOut> : Activity<TOut>
{
    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    public ExpressionEvaluator(string predicate)
    {
        VisualBasic.SetSettingsForImplementation(this, VbSettings);

        Implementation = () => new Assign<TOut>
        {
            Value = new InArgument<TOut>(new VisualBasicValue<TOut>(predicate)),
            To = new ArgumentReference<TOut>("Result")
        };
    }

    public TOut EvalWith(TIn value)
    {
        return WorkflowInvoker.Invoke(this, new Dictionary<string, object> { { "Value", value } });
    }

    private static readonly VisualBasicSettings VbSettings;

    static ExpressionEvaluator()
    {
        VbSettings = new VisualBasicSettings();
        AddImports(typeof(TIn), VbSettings.ImportReferences);
        AddImports(typeof(TOut), VbSettings.ImportReferences);
    }

    private static void AddImports(Type type, ISet<VisualBasicImportReference> imports)
    {
        if (type.IsPrimitive || type == typeof(void) || type.Namespace == "System")
            return;

        var wasAdded = imports.Add(new VisualBasicImportReference { Assembly = type.Assembly.GetName().Name, Import = type.Namespace });

        if (!wasAdded)
            return;

        if (type.BaseType != null)
            AddImports(type.BaseType, imports); 

        foreach (var interfaceType in type.GetInterfaces())
            AddImports(interfaceType, imports);

        foreach (var property in type.GetProperties())
            AddImports(property.PropertyType, imports);

        foreach (var method in type.GetMethods())
        {
            AddImports(method.ReturnType, imports);

            foreach (var parameter in method.GetParameters())
                AddImports(parameter.ParameterType, imports);

            if (method.IsGenericMethod)
            {
                foreach (var genericArgument in method.GetGenericArguments())
                    AddImports(genericArgument, imports);
            }
        }

        if (type.IsGenericType)
        {
            foreach (var genericArgument in type.GetGenericArguments())
                AddImports(genericArgument, imports);
        }
    }
}
公共类评估:NativeActivity
{
[必需参数]
公共InArgument表达式文本{get;set;}
[必需参数]
公共InArgument值{get;set;}
受保护的覆盖无效执行(NativeActivityContext上下文)
{
var result=new ExpressionEvaluator(ExpressionText.Get(context)).EvalWith(Value.Get(context));
Result.Set(上下文、结果);
}
}
公共类ExpressionEvaluator:活动
{
[必需参数]
公共InArgument值{get;set;}
public ExpressionEvaluator(字符串谓词)
{
VisualBasic.SettingsForImplementation(这是VbSettings);
实现=()=>新分配
{
值=新的InArgument(新的VisualBasicValue(谓词)),
To=新参数参考(“结果”)
};
}
公众兜售评估(锡价)
{
返回WorkflowInvoker.Invoke(这个,新字典{{“Value”,Value});
}
专用静态只读VisualBasicSettings VbSettings;
静态表达式赋值器()
{
VbSettings=新的VisualBasicSettings();
AddImports(TIn的类型、VbSettings.ImportReferences);
AddImports(typeof(TOut)、VbSettings.ImportReferences);
}
专用静态void附加导入(类型,ISet导入)
{
if(type.IsPrimitive | | type==typeof(void)| | type.Namespace==System)
返回;
var wasAdded=imports.Add(新的VisualBasicImportReference{Assembly=type.Assembly.GetName().Name,Import=type.Namespace});
如果(!已添加)
返回;
if(type.BaseType!=null)
AddImports(type.BaseType,导入);
foreach(type.GetInterfaces()中的var interfaceType)
AddImports(接口类型、导入);
foreach(类型.GetProperties()中的var属性)
AddImports(property.PropertyType,导入);
foreach(类型.GetMethods()中的var方法)
{
AddImports(method.ReturnType,imports);
foreach(方法.GetParameters()中的var参数)
AddImports(parameter.ParameterType,导入);
if(method.IsGenericMethod)
{
foreach(方法.GetGenericArguments()中的变量genericArgument)
附加进口(一般辩论、进口);
}
}
if(type.IsGenericType)
{
foreach(类型为.GetGenericArguments()的变量genericArgument)
附加进口(一般辩论、进口);
}
}
}
编辑:更新类以包含完整的程序集和命名空间导入,以免收到可怕(且没有帮助)的错误消息:

未声明“值”。由于其保护级别,可能无法访问

此外,将ExpressionEvaluator类移到外部并将其公开,以便您可以在WF外部使用它,如下所示:

new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);
newexpressionevaluator(“Value*Math.PI”).EvalWith
metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
Predicate.ExpressionText = ExpressionText.Get(context);
public class Eval<T, TResult> : Activity<TResult>
{
    public string Expression { get; set; }

    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    protected override Func<Activity> Implementation
    {
        get
        {
            if (string.IsNullOrEmpty(Expression))
            {
                return base.Implementation;
            }

            return () => new Assign<TResult>
            {
                Value = new InArgument<TResult>(new VisualBasicValue<TResult>(Expression)),
                To = new ArgumentReference<TResult>("Result")
            };
        }
        set
        {
            throw new NotSupportedException();
        }
    }
}
var activity = new Eval<int, int>() { Expression = "Value + 2" };

var inputArgs = new Dictionary<string, object>()
{
    { "Value", 5 }
};

Console.WriteLine("RESULT: " + WorkflowInvoker.Invoke<int>(activity, inputArgs));
public class NativeEval<T, TResult> : NativeActivity<TResult>
{
    [RequiredArgument]
    public InArgument<string> ExpressionText { get; set; }
    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    private Assign Assign { get; set; }
    private VisualBasicValue<TResult> Predicate { get; set; }
    private Variable<TResult> ResultVar { get; set; }

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);

        Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
        ResultVar = new Variable<TResult>("ResultVar");
        Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };

        metadata.AddImplementationVariable(ResultVar);
        metadata.AddImplementationChild(Assign);
    }

    protected override void Execute(NativeActivityContext context)
    {
        // this line, commented or not, is the same!
        Predicate.ExpressionText = ExpressionText.Get(context);
        context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
    }

    private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
    {
        // the result will always be the ExpressionText.Length 
        Result.Set(context, ResultVar.Get(context));
    }
}
public class Evaluate<TIn, TOut> : NativeActivity<TOut>
{
    [RequiredArgument]
    public InArgument<string> ExpressionText { get; set; }

    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        var result = new ExpressionEvaluator<TIn, TOut>(ExpressionText.Get(context)).EvalWith(Value.Get(context));
        Result.Set(context, result);
    }
}

public class ExpressionEvaluator<TIn, TOut> : Activity<TOut>
{
    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    public ExpressionEvaluator(string predicate)
    {
        VisualBasic.SetSettingsForImplementation(this, VbSettings);

        Implementation = () => new Assign<TOut>
        {
            Value = new InArgument<TOut>(new VisualBasicValue<TOut>(predicate)),
            To = new ArgumentReference<TOut>("Result")
        };
    }

    public TOut EvalWith(TIn value)
    {
        return WorkflowInvoker.Invoke(this, new Dictionary<string, object> { { "Value", value } });
    }

    private static readonly VisualBasicSettings VbSettings;

    static ExpressionEvaluator()
    {
        VbSettings = new VisualBasicSettings();
        AddImports(typeof(TIn), VbSettings.ImportReferences);
        AddImports(typeof(TOut), VbSettings.ImportReferences);
    }

    private static void AddImports(Type type, ISet<VisualBasicImportReference> imports)
    {
        if (type.IsPrimitive || type == typeof(void) || type.Namespace == "System")
            return;

        var wasAdded = imports.Add(new VisualBasicImportReference { Assembly = type.Assembly.GetName().Name, Import = type.Namespace });

        if (!wasAdded)
            return;

        if (type.BaseType != null)
            AddImports(type.BaseType, imports); 

        foreach (var interfaceType in type.GetInterfaces())
            AddImports(interfaceType, imports);

        foreach (var property in type.GetProperties())
            AddImports(property.PropertyType, imports);

        foreach (var method in type.GetMethods())
        {
            AddImports(method.ReturnType, imports);

            foreach (var parameter in method.GetParameters())
                AddImports(parameter.ParameterType, imports);

            if (method.IsGenericMethod)
            {
                foreach (var genericArgument in method.GetGenericArguments())
                    AddImports(genericArgument, imports);
            }
        }

        if (type.IsGenericType)
        {
            foreach (var genericArgument in type.GetGenericArguments())
                AddImports(genericArgument, imports);
        }
    }
}
new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);