3

VB.NET 式 (たとえばデータベースから) を含む文字列を受け入れ、ワークフローの現在のスコープで使用可能な変数を使用してその文字列を評価し、結果を返す単純な WF4 アクティビティを作成しようとしています。残念ながら、私が試した方法では、プレーンオンであろうとActivity本格的なオンであろうとNativeActivity、壁にぶつかり続けています。

私の最初の試みは単純なアクティビティで、オブジェクトを入力として与えられた式を評価する単純なクラスを作成することができました。

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 } });
    }
}

これはうまくいき、次の式は 7 に評価されます。

new Eval<int, int>("Value + 2").EvalWith(5)

残念ながら、式の文字列は としてではなくコンストラクターの引数として与えられるため、思い通りに使用することはInArgument<string>できません。そのため、ワークフローに簡単に組み込む (ドラッグ アンド ドロップする) ことができません。私の2番目の試みはNativeActivity、その厄介なコンストラクターパラメーターを取り除くために試して使用することでした:

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>();
        ResultVar = new Variable<TResult>("ResultVar");
        Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };

        metadata.AddVariable(ResultVar);
        metadata.AddChild(Assign);
    }

    protected override void Execute(NativeActivityContext context)
    {
        Predicate.ExpressionText = ExpressionText.Get(context);
        context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
    }

    private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
    {
        Result.Set(context, ResultVar.Get(context));
    }
}

NativeEval以下で実行してみました。

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

しかし、次の例外があります。

アクティビティ '1: NativeEval' は、アクティビティ '1: NativeEval' のスコープで宣言されているため、この変数にアクセスできません。アクティビティは、独自の実装変数にのみアクセスできます。

だから私はに変更metadata.AddVariable(ResultVar);しましmetadata.AddImplementationVariable(ResultVar);たが、その後、別の例外が発生しました:

ワークフロー ツリーの処理中に次のエラーが発生しました: 'VariableReference': 参照された変数オブジェクト (名前 = 'ResultVar') は、このスコープでは表示されません。このスコープで表示される同じ名前の別の場所参照が存在する可能性がありますが、同じ場所を参照していません。

ここで.ScheduleFunc()説明されているようにアクティビティをスケジュールするために使用しようとしましたが、返された結果は常にでした(ただし、奇妙なことに、例外はスローされませんでした)。VisualBasicValuenull

私は困惑しています。WF4 のメタプログラミング モデルは のメタプログラミング モデルよりもはるかに難しいように見えますがSystem.Linq.Expressions、これは難しく、しばしば当惑します (通常のメタプログラミングのように) が、少なくとも私はそれに頭を悩ませることができました。単純な古いプログラムではなく、持続可能で再開可能、非同期、再配置可能なプログラムを表現する必要があるという複雑さが増しているためだと思います。


編集:私が経験している問題は、ハードコーディングされていない式を評価しようとしていることが原因であるとは思わないので、次の変更を に加えNativeActivityて、静的な式を持たせることができます:

交換

Predicate = new VisualBasicValue<TResult>();

Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");

そして、行を削除します

Predicate.ExpressionText = ExpressionText.Get(context);

これらの行で式が静的であっても、同じエラーが発生します。


EDIT2この記事は、私が得ていた例外に対処しました。変数と子アクティビティの両方を「実装」に変更する必要があったため、次のようにしました。

metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);

これに変更:

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

そして、すべての例外がなくなりました。残念ながら、次の行はまったく何もしないことが明らかになりました。

Predicate.ExpressionText = ExpressionText.Get(context);

ExpressionText実行時に a のプロパティを変更しVisualBasicValueても効果はありません。ILSpy で簡単に確認すると、その理由が明らかになります。式のテキストは、CacheMetadata()が呼び出されたときにのみ評価され、式ツリーに変換されます。その時点では、式はまだわかりません。ノーオペレーション。NativeActivityMetadata独自の CacheMetadata オーバーライド メソッドで取得したオブジェクトを保存してから、リフレクションを使用してVisualBasicValue'sへの呼び出しを強制しようとCacheMetadata()しましたが、別の不可解な例外 (AmbiguousMatchException タイプの「あいまいな一致が見つかりました。」) がスローされました。

現時点では、動的式をワークフローに完全に統合して、スコープ内のすべての変数をワークフローに公開することはできないようです。Evalクラス内でクラスで使用されるメソッドがあると思いますNativeEval

4

3 に答える 3

3

私は次のアクティビティを使用することになりました。ワークフローの変数にアクセスすることはできません。代わりに、動的式内で同じ名前で使用できる単一の引数「値」を受け入れます。それ以外はかなりうまくいきます。

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);
        }
    }
}

編集:恐ろしい(そして役に立たない)エラーメッセージが表示されないように、完全なアセンブリと名前空間のインポートを含むようにクラスを更新しました:

「値」は宣言されていません。保護レベルが原因でアクセスできない場合があります。

また、ExpressionEvaluatorクラスを外部に移動して公開し、次のようにWFの外部で使用できるようにしました。

new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);

どちらが返されますか:

6.28318530717959

于 2012-05-29T12:46:14.133 に答える
0

これには別のフレームワークを使用することをお勧めします。良いアプローチの 1 つは、nCalc を使用することです。http://ncalc.codeplex.com/

任意の式を解析し、静的または動的パラメーターとカスタム関数を含む結果を評価できます。

実行時にさまざまな種類の式を評価するために使用します。

于 2012-04-23T18:19:31.070 に答える
0

「述語」が既知の文字列であり、実行時に評価される式である必要がない場合は、InArgumentを破棄してコンストラクターを回避することで、次のようなことができます。

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));

EDITPredicate.ExpressionText :コメントされていなくても、何の効果もないことを確認してください:

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));
    }
}

メソッドをExecute()変更しても、子の実装は効果がありません。実行モードはオンで、子ツリーは変更できません。

于 2012-04-23T23:30:04.323 に答える