[WF 4 (8)] アクティビティのデリゲート (Activity Delegates)

環境 : Visual Studio 2010 Beta 2 (.NET Framework 4)

WF 4

  1. そのアイデアとベースクラスの変更
  2. コードいらずのワークフロー (入門)
  3. ワークフローのコードと XAML の内部
  4. フローチャート (FlowCharts)
  5. アクティビティ (基礎)
  6. アクティビティコンテキスト (context) と変数 (Variable) の意義
  7. アクティビティにおけるさまざまな実行管理
  8. アクティビティのデリゲート
  9. アクティビティの非同期実行
  10. アクティビティデザイナー
  11. ブックマーク
  12. Workflow Extensions と 永続化、トラッキング
  13. デザイナーリホスティングとカスタムアプリケーション

こんにちは。

WF 4 における NativeActivityContext を使った柔軟な実行管理メカニズム (の続き) として、今回は、Activity Delegates について記載します。

定義と実行

WF で扱うワークフローには、定義 (Definition) と実行 (Execution) という 2 つの概念があります。例えば、以下の簡単なワークフロー実行では、前半がこの workflow1 の「定義」であり、Invoke が「実行」に相当します。

static void Main(string[] args)
{
    // ワークフローの定義
    Activity workflow1 = new Sequence()
    {
        Activities =
        {
            new WriteLine()
            {
                Text = "Hello !"
            }
        }
    };

    // ワークフローの実行
    WorkflowInvoker.Invoke(workflow1);
}

そして、定義段階に指定しなければならないものと、実行中に指定できるものが明確に区別されています。WF では実行時にアクティビティツリーの検証などをおこなっており、「定義」されていないことを「実行」しようとすると、例外の発生など、おかしなことが起こります。(例えば、ワークフローの実行中に、上記の「ワークフローの定義」と同じことをコードでおこなうと、例外が発生します。)

【注意】 第 6 回のアクティビティコンテキストの話でも実感していただけたかもしれませんが、 WF における「定義」、「実行」とは、プログラミング言語 (この場合は C#) の「定義」、「オブジェクト」とは異なっているので注意をしてください。言語で「定義」に相当するものは「クラス」、実行されるオブジェクトに相当するものは「インスタンス」になりますが、上記のコードの通り、ワークフローの定義 (Definition) は、言語でいうところの「インスタンス」として与えられ、実行されるそれぞれの状態は「コンテキスト」になります。
なお、そのワークフローの中で使用されている CodeActivity, NativeActivity などの “定義” は、言語の「クラス」に相当しています。

アクティビティ・デリゲートの存在意義

さて、アクティビティの中で、子アクティビィに引数 (Argument) を与えてを実行するには、あらかじめ、その子アクティビティに引数を宣言 (=定義 !) しておかなければいけません。(実行時にコードなどで動的に引数を作成して与えるとエラーになります。)
ところが、下図の ForEach アクティビティの例をみてください。ForEach の中で実行される子アクティビティでは、実行時に引数 (下図の item) の値を 都度 使用しなければなりません。

こうした場合に使用できるメカニズムが、今回紹介する Activity Delegates というメカニズムです。

【注意】 なお、注釈が多くすみませんが、第 2 回を思い出して頂けるとおわかりのように、アクティビティの中に都度必要な Variable を追加することが可能です。これをおこなうには、以下のようにメタデータで SetVariablesCollection をセットしておきます (注 : これまで使用してきた SetImplementationVariablesCollection のほうではありません !)。このようにアクティビティを作成すると、第 2 回の Sequence アクティビティのように、そのスコープ内で 「ワークフロー変数」を作成することができます。
この方式を使うと、必要な変数を都度 ワークフロー作成時に宣言して、そこに動的に値を入れれば良いように思うかもしれませんが、この仕組みは、まさに第 2 回のようにスコープ内で使用可能な「ワークフロー変数」(データの受け渡しをおこなうための変数) を宣言するものであり、下記のアクティビティ自体の内部には、まだ目的となる Variable が “定義” されていないため、このアクティビティの中からコードなどを使用してこの変数の内容の書き換えをおこなおうとしてもやはり例外となります。(つまり、今回のような目的で使用するものではありません。)

public sealed class TestActivity : NativeActivity
{
    private Collection<Variable> variables = new Collection<Variable>();
    public Collection<Variable> Variables
    {
        get
        {
            return this.variables;
        }
    }

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

    . . . . .

アクティビティ・デリゲートを使用したアクティビティ

では、ノウガキはこの辺にして、実際にサンプルコードをみて実感して頂きましょう。

Activity Delegates では、”架空のアクティビティ” に相当する ActivityAction と ActivityFunc を使用します。前者は返り値なし、後者は返り値ありの場合に使用するクラスです。

以下のサンプルコードをみてください。
以下の Activity1 の中では、この “架空のアクティビティ” に相当する ActivityAction を “宣言” しており、ScheduleAction メソッドを見ていただくとわかりますが、この “架空のアクティビティ” は、「引数を 1 つだけ渡すことができる」と仮定しています。(下記の ScheduleAction メソッドでは、引数は 16 個まで使用できます。)
そして、Main の中の “ワークフローの宣言” では、この ActionActivity に “実際のアクティビティ” を “宣言” して割り当てています。

このアクティビティは、ForEach アクティビティのように、内部で使用する子アクティビティを外から “定義” できると共に、そこに渡す引数 (Argument) の “定義” もおこなっておき、実際に、この子アクティビティに引数 (Argument) を渡しているのは Activity1 の内部になります。

・・・
using System.Activities;
using System.Activities.Statements;

・・・

static void Main(string[] args)
{
  // ワークフローの定義 :
  // 外から、子アクティビティと、渡す引数の定義 (VB式) のみを指定する
  Activity1 activity1 = new Activity1();
  ActivityAction<string> action = new ActivityAction<string>();
  WriteLine write = new WriteLine();
  DelegateInArgument<string> argument = new DelegateInArgument<string>("itemtest");
  action.Argument = argument;
  write.Text = new InArgument<string>((context) => "This is " + action.Argument.Get(context));
  action.Handler = write;
  activity1.MyAction = action;

  // ワークフローを実行
  WorkflowInvoker.Invoke(activity1);

  Console.ReadLine();
}

・・・

public class Activity1 : NativeActivity
{
  public ActivityAction<string> MyAction { get; set; }

  protected override void CacheMetadata(NativeActivityMetadata metadata)
  {
    metadata.AddDelegate(MyAction);
  }

  protected override void Execute(NativeActivityContext context)
  {
    // ここで、上記の Argument として実際の引数の値 (ccc という文字列) を渡す
    context.ScheduleAction<string>(MyAction, "ccc");
  }
}

この実行結果は、

This is ccc

となります。

要約すると、Activity1 では、”実際のアクティビティ” ではなく “架空のアクティビティ” と “架空の引数” (Argument) をもとに処理を記述 (定義) し、この Activity1 を使ってワークフロー定義をおこなう際に、実際のアクティビティと引数を “定義” するといった具合にアクティビティ (ワークフロー) を構成するというスタイルです。

ここでは ActivityAction を例に説明していますが、前述した ActivityFunc の場合には、同様に ScheduleFunc を呼び出します。
なお、ActivityAction と ActivityFunc の双方の基底(親)クラスに相当する ActivityDelegate というクラスがあり、同様に ScheduleDelegate というメソッドで呼び出すことが可能です。(ActivityAction と ActivityFunc を使った処理のスーパーセットになっています。)

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s