ワークフロー定義は、構成されたアクティビティ オブジェクトのツリーです。 このアクティビティツリーは、XAML の手動編集やワークフロー デザイナーを使用して XAML を生成するなど、さまざまな方法で定義できます。 ただし、XAML の使用は必須ではありません。 ワークフロー定義は、プログラムで作成することもできます。 このトピックでは、コードを使用したワークフロー定義、アクティビティ、および式の作成の概要について説明します。 コードを使用して XAML ワークフローを操作する例については、「XAML との間での ワークフローとアクティビティのシリアル化」を参照してください。
ワークフロー定義の作成
ワークフロー定義を作成するには、アクティビティの種類のインスタンスをインスタンス化し、アクティビティ オブジェクトのプロパティを構成します。 子アクティビティを含まないアクティビティの場合は、数行のコードを使用してこれを実現できます。
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
注
このトピックの例では、 WorkflowInvoker を使用してサンプル ワークフローを実行します。 ワークフローの呼び出し、引数の渡し、使用可能なさまざまなホスティングの選択肢の詳細については、「 WorkflowInvoker と WorkflowApplication の使用」を参照してください。
この例では、1 つの WriteLine アクティビティで構成されるワークフローが作成されます。 WriteLine アクティビティのText引数が設定され、ワークフローが呼び出されます。 アクティビティに子アクティビティが含まれている場合、構築方法は似ています。 次の例では、2 つのWriteLine アクティビティを含むSequence アクティビティを使用します。
Activity wf = new Sequence
{
Activities =
{
new WriteLine
{
Text = "Hello"
},
new WriteLine
{
Text = "World."
}
}
};
WorkflowInvoker.Invoke(wf);
オブジェクト初期化子の使用
このトピックの例では、オブジェクトの初期化の構文を使用します。 オブジェクトの初期化の構文は、コードでワークフロー定義を作成する場合に便利な方法です。これは、ワークフローのアクティビティの階層ビューが提供され、アクティビティ間の関係が示されるためです。 プログラムからワークフローを作成する場合に、オブジェクトの初期化の構文を使用しなければならないという要件はありません。 次の例は、機能的には前のサンプルと同じです。
WriteLine hello = new WriteLine();
hello.Text = "Hello";
WriteLine world = new WriteLine();
world.Text = "World";
Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);
WorkflowInvoker.Invoke(wf);
オブジェクト初期化子の詳細については、「 方法: コンストラクターを呼び出さずにオブジェクトを初期化する (C# プログラミング ガイド)」 および 「方法: オブジェクト初期化子を使用してオブジェクトを宣言する」を参照してください。
変数、リテラル値、および式の操作
コードを使用してワークフロー定義を作成する場合は、ワークフロー定義の作成の一環として実行されるコードと、そのワークフローのインスタンスの実行の一部として実行されるコードに注意してください。 たとえば、次のワークフローは、乱数を生成してコンソールに書き込むものです。
Variable<int> n = new Variable<int>
{
Name = "n"
};
Activity wf = new Sequence
{
Variables = { n },
Activities =
{
new Assign<int>
{
To = n,
Value = new Random().Next(1, 101)
},
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
}
};
このワークフロー定義コードが実行されると、 Random.Next
の呼び出しが行われ、結果がリテラル値としてワークフロー定義に格納されます。 このワークフローの多くのインスタンスを呼び出すことができるので、すべて同じ番号が表示されます。 ワークフローの実行中に乱数の生成を行うには、ワークフローを実行するたびに評価される式を使用する必要があります。 次の例では、Visual Basic 式を VisualBasicValue<TResult>と共に使用します。
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
前の例の式は、 CSharpValue<TResult> と C# 式を使用して実装することもできます。
new Assign<int>
{
To = n,
Value = new CSharpValue<int>("new Random().Next(1, 101)")
}
C# 式を含むワークフローが呼び出される前に、C# 式をコンパイルする必要があります。 C# 式がコンパイルされていない場合は、次のようなメッセージでワークフローが呼び出されたときに NotSupportedException がスローされます。 Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled.
Visual Studio で作成されたワークフローに関連するほとんどのシナリオでは、C# 式は自動的にコンパイルされますが、コード ワークフローなどの一部のシナリオでは、C# 式を手動でコンパイルする必要があります。 C# 式をコンパイルする方法の例については、「C# 式」トピックの「コード ワークフローでの C# 式の使用」セクションを参照してください。
VisualBasicValue<TResult>は、式の r 値として使用できる Visual Basic 構文の式を表し、CSharpValue<TResult>は、式の r 値として使用できる C# 構文の式を表します。 これらの式は、含まれているアクティビティが実行されるたびに評価されます。 式の結果はワークフロー変数 n
に割り当てられ、これらの結果はワークフロー内の次のアクティビティによって使用されます。 実行時に n
ワークフロー変数の値にアクセスするには、 ActivityContext が必要です。 これは、次のラムダ式を使用してアクセスできます。
注
これらのコードの両方がプログラミング言語として C# を使用している例ですが、1 つは VisualBasicValue<TResult> を使用し、1 つは CSharpValue<TResult>を使用していることに注意してください。 VisualBasicValue<TResult> および CSharpValue<TResult> は、Visual Basic プロジェクトと C# プロジェクトの両方で使用できます。 既定では、ワークフロー デザイナーで作成された式は、ホスティング プロジェクトの言語と一致します。 コードでワークフローを作成する場合、目的の言語はワークフロー作成者の判断で行われます。
これらの例では、式の結果がワークフロー変数 n
に割り当てられ、これらの結果はワークフロー内の次のアクティビティによって使用されます。 実行時に n
ワークフロー変数の値にアクセスするには、 ActivityContext が必要です。 これは、次のラムダ式を使用してアクセスできます。
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
ラムダ式の詳細については、「 ラムダ式 (C# リファレンス) 」または 「ラムダ式 (Visual Basic)」を参照してください。
ラムダ式は XAML 形式にシリアル化できません。 ラムダ式を使用してワークフローをシリアル化しようとすると、 LambdaSerializationException "このワークフローには、コードで指定されたラムダ式が含まれています。 これらの式は XAML シリアル化できません。 ワークフローの XAML シリアル化を可能にするには、VisualBasicValue/VisualBasicReference または ExpressionServices.Convert(lambda) を使用します。 これにより、ラムダ式が式アクティビティに変換されます。この式を XAML と互換性のあるものにするには、次の例に示すように、 ExpressionServices と Convertを使用します。
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}
VisualBasicValue<TResult>も使用できます。 Visual Basic 式を使用する場合、ラムダ式は必要ありません。
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
//Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}
実行時に、Visual Basic 式は LINQ 式にコンパイルされます。 前の例はどちらも XAML にシリアル化できますが、シリアル化された XAML がワークフロー デザイナーで表示および編集されることを意図している場合は、式に VisualBasicValue<TResult> を使用します。
ExpressionServices.Convert
を使用するシリアル化されたワークフローはデザイナーで開くことができますが、式の値は空白になります。 ワークフローを XAML にシリアル化する方法の詳細については、「XAML との間 でのワークフローとアクティビティのシリアル化」を参照してください。
リテラル式と参照型
リテラル式は、 Literal<T> アクティビティによってワークフローで表されます。 次の WriteLine アクティビティは機能的に同等です。
new WriteLine
{
Text = "Hello World."
},
new WriteLine
{
Text = new Literal<string>("Hello World.")
}
String以外の参照型を使用してリテラル式を初期化することは無効です。 次の例では、 Assign アクティビティの Value プロパティは、 List<string>
を使用してリテラル式で初期化されます。
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new List<string>())
},
このアクティビティを含むワークフローが検証されると、次の検証エラーが返されます。"Literal では、値型と不変型 System.String のみがサポートされます。 System.Collections.Generic.List'1[System.String] 型はリテラルとして使用できません。"ワークフローが呼び出されると、検証エラーのテキストを含む InvalidWorkflowException がスローされます。 参照型でリテラル式を作成しても、ワークフローの各インスタンスに対して参照型の新しいインスタンスが作成されないため、これは検証エラーです。 これを解決するには、リテラル式を、参照型の新しいインスタンスを作成して返す式に置き換えます。
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))
},
式の詳細については、「 式」を参照してください。
式と InvokeMethod アクティビティを使用したオブジェクトに対するメソッドの呼び出し
InvokeMethod<TResult> アクティビティを使用して、.NET Framework 内のクラスの静的メソッドとインスタンス メソッドを呼び出すことができます。 このトピックの前の例では、 Random クラスを使用して乱数が生成されました。
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
InvokeMethod<TResult> アクティビティを使用して、Random クラスのNext メソッドを呼び出すこともできます。
new InvokeMethod<int>
{
TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),
MethodName = "Next",
Parameters =
{
new InArgument<int>(1),
new InArgument<int>(101)
},
Result = n
}
Nextは静的メソッドではないため、Random クラスのインスタンスが TargetObject プロパティに提供されます。 この例では、Visual Basic 式を使用して新しいインスタンスを作成しますが、以前に作成してワークフロー変数に格納することもできます。 この例では、InvokeMethod<TResult> アクティビティではなく、Assign<T> アクティビティを使用する方が簡単です。 メソッド呼び出しが最終的に Assign<T> または InvokeMethod<TResult> アクティビティによって呼び出される場合、 InvokeMethod<TResult> には RunAsynchronously プロパティがあるため、利点があります。 このプロパティを true
に設定すると、呼び出されたメソッドはワークフローに関して非同期的に実行されます。 他のアクティビティが並行している場合、メソッドが非同期的に実行されている間はブロックされません。 また、呼び出すメソッドに戻り値がない場合は、 InvokeMethod<TResult> がメソッドを呼び出す適切な方法です。
引数と動的アクティビティ
ワークフロー定義は、アクティビティをアクティビティ ツリーにアセンブルし、プロパティと引数を構成することによって、コード内に作成されます。 既存の引数はバインドできますが、新しい引数をアクティビティに追加することはできません。 これには、ルート アクティビティに渡されるワークフロー引数が含まれます。 命令型コードでは、ワークフロー引数は新しい CLR 型のプロパティとして指定され、XAML では x:Class
と x:Member
を使用して宣言されます。 ワークフロー定義がメモリ内オブジェクトのツリーとして作成されるときに新しい CLR 型が作成されないため、引数を追加できません。 ただし、引数は DynamicActivityに追加できます。 この例では、2 つの整数引数を受け取り、それらを加算して結果を返す DynamicActivity<TResult> が作成されます。 各引数に対してDynamicActivityPropertyが作成され、操作の結果がDynamicActivity<TResult>のResult引数に割り当てられます。
InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();
DynamicActivity<int> wf = new DynamicActivity<int>
{
Properties =
{
new DynamicActivityProperty
{
Name = "Operand1",
Type = typeof(InArgument<int>),
Value = Operand1
},
new DynamicActivityProperty
{
Name = "Operand2",
Type = typeof(InArgument<int>),
Value = Operand2
}
},
Implementation = () => new Sequence
{
Activities =
{
new Assign<int>
{
To = new ArgumentReference<int> { ArgumentName = "Result" },
Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
}
}
}
};
Dictionary<string, object> wfParams = new Dictionary<string, object>
{
{ "Operand1", 25 },
{ "Operand2", 15 }
};
int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);
動的アクティビティの詳細については、「 実行時のアクティビティの作成」を参照してください。
コンパイル済みアクティビティ
動的アクティビティは、コードを使用して引数を含むアクティビティを定義する 1 つの方法ですが、アクティビティはコード内で作成して型にコンパイルすることもできます。 CodeActivityから派生する単純なアクティビティと、AsyncCodeActivityから派生する非同期アクティビティを作成できます。 これらのアクティビティは、引数を持ち、値を返し、命令型コードを使用してロジックを定義できます。 これらの種類のアクティビティを作成する例については、「 CodeActivity 基本クラス 」および 「非同期アクティビティの作成」を参照してください。
NativeActivityから派生したアクティビティは、命令型コードを使用してロジックを定義できます。また、ロジックを定義する子アクティビティを含めることもできます。 また、ブックマークの作成など、ランタイムの機能にもフル アクセスできます。 NativeActivity ベースのアクティビティを作成する例については、「NativeActivity 基本クラス」、「方法: アクティビティを作成する」、およびネイティブ アクティビティを使用したカスタム複合のサンプルを参照してください。
Activityから派生するアクティビティは、子アクティビティを使用してのみロジックを定義します。 通常、これらのアクティビティはワークフロー デザイナーを使用して作成されますが、コードを使用して定義することもできます。 次の例では、Activity<int>
から派生するSquare
アクティビティが定義されています。
Square
アクティビティには、Value
という名前の 1 つのInArgument<T>があり、Implementation プロパティを使用してSequence アクティビティを指定してロジックを定義します。
Sequence アクティビティには、WriteLine アクティビティとAssign<T> アクティビティが含まれています。 これら 3 つのアクティビティを組み合わせて、 Square
アクティビティのロジックを実装します。
class Square : Activity<int>
{
[RequiredArgument]
public InArgument<int> Value { get; set; }
public Square()
{
this.Implementation = () => new Sequence
{
Activities =
{
new WriteLine
{
Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))
},
new Assign<int>
{
To = new OutArgument<int>((env) => this.Result.Get(env)),
Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))
}
}
};
}
}
次の例では、1 つの Square
アクティビティで構成されるワークフロー定義が、 WorkflowInvokerを使用して呼び出されます。
Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};
int result = WorkflowInvoker.Invoke(new Square(), inputs);
Console.WriteLine("Result: {0}", result);
ワークフローが呼び出されると、次の出力がコンソールに表示されます。
値の四分の一: 5
結果: 25
.NET