命令型コードを使用してワークフロー、アクティビティ、および式を作成する方法
ワークフロー定義は、構成済みのアクティビティ オブジェクトのツリーです。 このアクティビティ ツリーは、手動で XAML を編集したり、ワークフロー デザイナーを使用して XAML を生成したりするなど、多くの方法で定義することができます。 ただし、XAML の使用は必須ではありません。 ワークフロー定義は、プログラムで作成することもできます。 このトピックでは、コードを使用したワークフローの定義、アクティビティ、および式の作成の概要について説明します。 コードを使用して XAML ワークフローを操作する例については、XAML との間のワークフローとアクティビティのシリアル化に関する記事をご覧ください。
ワークフロー定義の作成
アクティビティ型のインスタンスをインスタンス化して、アクティビティ オブジェクトのプロパティを構成することで、ワークフロー定義を作成できます。 子アクティビティを含まないアクティビティの場合、数行のコードを使用してこれを作成できます。
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
注意
このトピックの例では、WorkflowInvoker を使用してサンプル ワークフローを実行します。 ワークフローの呼び出し、引数の受け渡し、および使用可能なさまざまなホスティングの選択肢の詳細については、「WorkflowInvoker と WorkflowApplication の使用」を参照してください。
次の例では、1 つの WriteLine アクティビティから成るワークフローを作成します。 WriteLine アクティビティの Text 引数が設定され、ワークフローが呼び出されます。 アクティビティに子アクティビティが含まれる場合も、作成のメソッドは同じです。 次の例では、2 つの Sequence アクティビティを含む WriteLine アクティビティを使用します。
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
への呼び出しが実行され、その結果がリテラル値としてワークフロー定義に保存されます。 このワークフローの多くのインスタンスを呼び出すことができ、そのすべてに同じ数字が表示されます。 ワークフローの実行中にランダムな数値を生成するには、ワークフローを実行するたびに評価する式を使用する必要があります。 次の例では、VisualBasicValue<TResult> で Visual Basic 式を使用します。
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# 式がコンパイルされていない場合、ワークフローが呼び出されると 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> は式の右辺値として使用できる Visual Basic 構文の式を表し、CSharpValue<TResult> は式の右辺値として使用できる C# 構文の式を表します。 これらの式は、含まれるアクティビティが実行されるたびに評価されます。 式の結果はワークフローの変数 n
に代入され、これらの結果はワークフローの次のアクティビティによって使用されます。 実行時にワークフローの変数 n
の値にアクセスするには、ActivityContext が必要です。 次のようなラムダ式を使用するとアクセスできます。
注意
この 2 つのコードはプログラミング言語として 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>())
},
このアクティビティを含むワークフローを検証すると、"リテラルは、値の型と変更不可の型 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> アクティビティは、Next クラスの Random メソッドの呼び出しにも使用されました。
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 式を使用して作成されますが、これまでも作成されてワークフロー変数に格納されている場合があります。 この例では、Assign<T> アクティビティの代わりに、InvokeMethod<TResult> アクティビティをより簡単に使用しています。 Assign<T> アクティビティまたは InvokeMethod<TResult> アクティビティで最終的に呼び出されるメソッド呼び出しが長時間実行されている場合、InvokeMethod<TResult> には RunAsynchronously プロパティがあるため、効果があります。 このプロパティが true
に設定されると、呼び出されるメソッドはワークフローに対して非同期に実行されます。 他のアクティビティが並列実行される場合、そのメソッドは非同期に実行され、それらのアクティビティはブロックされません。 また、呼び出すメソッドに戻り値がない場合、InvokeMethod<TResult> はメソッドを呼び出すための適切な手段となります。
引数と動的なアクティビティ
アクティビティをアクティビティ ツリーにまとめてプロパティと引数を構成すると、コードでワークフロー定義を作成できます。 既存の引数を見つけることはできますが、新しい引数をアクティビティに追加することはできません。 これには、ルート アクティビティに渡されるワークフロー引数が含まれます。 ワークフロー引数は、命令型コードでは新しい CLR 型のプロパティとして指定され、XAML では x:Class
および x:Member
を使用して宣言されます。 ワークフロー定義がメモリ内オブジェクトのツリーとして作成された場合は新しい CRL 型が作成されないため、引数を追加できません。 ただし、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 から派生するアクティビティは、子アクティビティを使用してロジックだけを定義します。 これらのアクティビティは、通常、ワークフロー デザイナーを使用して作成されますが、コードを使用して定義することもできます。 次の例では、Square
から派生する Activity<int>
アクティビティが定義されます。 Square
アクティビティには InArgument<T> という名前の 1 つの Value
があり、そのロジックが Sequence プロパティを使用して Implementation アクティビティを指定してロジックを定義します。 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))
}
}
};
}
}
次の例では、単一の Square
アクティビティで構成されるワークフロー定義を WorkflowInvoker を使用して呼び出します。
Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};
int result = WorkflowInvoker.Invoke(new Square(), inputs);
Console.WriteLine("Result: {0}", result);
このワークフローが呼び出されると、次の出力がコンソールに表示されます。
Squaring the value: 5
Result: 25
.NET