[原文へのリンク] (https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation) (google翻訳)

IResult and Coroutines

以前、私は、コルーチンと呼ばれるアクションの概念のもう一つの魅力的な機能があったことを述べました。あなたが前にその言葉を聞いたことがない場合は、ここでウィキペディアが言っているものです。

コンピュータサイエンスでは、コルーチンは停止させ、特定の場所で実行を再開するための複数のエントリポイントを許可するようにサブルーチンを一般プログラムコンポーネントがあります。コルーチンは、協調作業、イテレータ、無限リストやパイプなどのより身近なプログラムコンポーネントを実装するための非常に適しています。

ここでは、それについての事ができ一つの方法です:その後、メソッドを実行し、それはいくつかのステートメントの実行だ一時停止することができることを想像し、戻ってきて、その後、他の何かを移動し、中断したところから実行を再開。この技術は、これらのタスクは非同期に実行する必要がある場合は特に、タスクベースのプログラミングでは非常に強力です。例えば、それは、その結果を取り、その上にいくつかの作業を行うと非同期に、別のWebサービスを呼び出す必要がある、我々は非同期Webサービスを呼び出す必要があるのViewModelを持っているとしましょう。最後に、その後、モーダルダイアログに結果を表示し、別の非同期タスクを使用してユーザーのダイアログ選択に応答する必要があります。標準的なイベント駆動型の非同期モデルでこれを達成することは楽しい経験ではありません。しかし、これは、コルーチンを使用することによって達成する簡単な作業です。問題... C#のネイティブコルーチンを実装していません。幸いなことに、我々は(一種の)イテレータの上にそれらを構築することができます。

Caliburn.Microでこの機能を利用するために必要な2つあります:まず、あなたが実行したいタスクを表す、いくつかのクラスのIResultインターフェイスを実装します。第二に、アクションからIResultのインスタンスをもたらします。のは、これをより具体的にしてみましょう。我々は、動的にダウンロードして、メインパッケージのショー画面一部ではありませんしたかったSilverlightアプリケーションを持っていたと言います。まず、おそらく非同期その後、「読み込み中」のインジケータを表示し、外部パッケージをダウンロード、次の「ロード」のインジケータを非表示にし、最終的に動的モジュール内の特定の画面に移動したいと思います。ここでのコードは、あなたの最初の画面が動的にロードされた第二の画面に移動するためにコルーチンを使用したい場合は次のようになります:

using System.Collections.Generic;
using System.ComponentModel.Composition;

[Export(typeof(ScreenOneViewModel))]
public class ScreenOneViewModel
{
    public IEnumerable<IResult> GoForward()
    {
        yield return Loader.Show("Downloading...");
        yield return new LoadCatalog("Caliburn.Micro.Coroutines.External.xap");
        yield return Loader.Hide();
        yield return new ShowScreen("ExternalScreen");
    }
}

まず、アクション「GoForward"はIEnumerableをの戻り値の型を持っていることがわかります。これはコルーチンを使用するために重要です。メソッドの本体は、4つのイールド文を持っています。これらのイールドはそれぞれ、IResultのインスタンスを返しています。最初は、ダウンロードXAPから新しい画面を表示するには、非同期的にXAPをダウンロードすることが第二の「ダウンロード」のメッセージを非表示にする第三及び第四の「ダウンロード」インジケータを表示する結果です。その特定のタスクが完了するまで、各yield文の後、コンパイラは、このメソッドの実行を「一時停止」になります。第二は、非同期であるが、第1、第3および第4のタスクは、同期しています。しかし、イールドの構文を使用すると、より読みやすくし、宣言的な構造として、元のワークフローを維持し、順番にすべてのコードを書くことができます。これがどのように機能するかもう少しを理解するために、IResultインターフェイスを見て:

public interface IResult
{
    void Execute(CoroutineExecutionContext context);
    event EventHandler<ResultCompletionEventArgs> Completed;
}

これは、実装するための非常に単純なインタフェースです。単に「実行」の方法でコードを書いて、あなたが完了したら、それは同期または非同期タスクであるかどうか、「完了」イベントを発生させるようにしてください。コルーチンは、アクションの内部で発生するため、我々は、UI関連IResultの実装を構築するのに有用なActionExecutionContextを提供します。これは、ビューモデルを宣言ビューまたは相互作用ベースのユニットテストの必要性への参照を持つことなく、ビューを制御するには、その意図を述べるための方法を可能にします。ここActionExecutionContextは次のようになります。

public class ActionExecutionContext
{
    public ActionMessage Message;
    public FrameworkElement Source;
    public object EventArgs;
    public object Target;
    public DependencyObject View;
    public MethodInfo Method;
    public Func<bool> CanExecute;
    public object this[string key];
}

そして、ここですべてのこれらのプロパティが何を意味するかについての説明は次のとおりです。

  • Message - このIResultの呼び出しの原因となった元ActionMessage。
  • Source - アクションの実行をトリガーしたFrameworkElement。
  • EventArgs - アクションのトリガーに関連付けられたイベント引数。
  • Target - 実際のアクションメソッドが存在するクラスインスタンス。
  • View - ターゲットに関連付けられているビュー。
  • Method - ターゲット·インスタンス上で呼び出すメソッドMETHODINFOの指定。
  • CanExecute - そうでない場合はfalseアクションを呼び出すことができる場合にtrueを返す関数、。
  • Key Index:場所は/ストア·フレームワークの拡張機能によって使用され得る任意の追加のメタデータを取得します。

このことを念頭に置いてを保有、私はVisualTreeのロードメッセージを表示するために使用するBusyIndicatorの最初のインスタンスを探して検索未処置ローダーIResultを書きました。ここでの実装は次のとおりです。

using System;
using System.Windows;
using System.Windows.Controls;

public class Loader : IResult
{
    readonly string message;
    readonly bool hide;

    public Loader(string message)
    {
        this.message = message;
    }

    public Loader(bool hide)
    {
        this.hide = hide;
    }

    public void Execute(CoroutineExecutionContext context)
    {
        var view = context.View as FrameworkElement;
        while(view != null)
        {
            var busyIndicator = view as BusyIndicator;
            if(busyIndicator != null)
            {
                if(!string.IsNullOrEmpty(message))
                    busyIndicator.BusyContent = message;
                busyIndicator.IsBusy = !hide;
                break;
            }

            view = view.Parent as FrameworkElement;
        }

        Completed(this, new ResultCompletionEventArgs());
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

    public static IResult Show(string message = null)
    {
        return new Loader(message);
    }

    public static IResult Hide()
    {
        return new Loader(true);
    }
}

私はcontext.Viewを利用した方法を参照してください?これは、ビューとビューモデルの間の分離を維持しながら、多くの可能性を開きます。ちょうどあなたがIResultの実装で行うことができますいくつかの興味深いリストする:、メッセージボックスを表示し、VMベースのモーダルダイアログを表示し、ユーザーのマウスの位置に仮想マシンベースのポップアップを表示、アニメーション、ショー·ファイル·セーブ/ロードダイアログを果たし、 、最大の機会の一つは、Webサービスを呼び出している。もちろんなどVMのプロパティではなく、コントロールに基づいて、特定のUI要素の場所にフォーカス。あなたはそれを、行う方法であるが、動的にXAPをダウンロードし、わずかに異なるシナリオを使用して見てみましょう:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.ReflectionModel;
using System.Linq;

public class LoadCatalog : IResult
{
    static readonly Dictionary<string, DeploymentCatalog> Catalogs = new Dictionary<string, DeploymentCatalog>();
    readonly string uri;

    [Import]
    public AggregateCatalog Catalog { get; set; }

    public LoadCatalog(string relativeUri)
    {
        uri = relativeUri;
    }

    public void Execute(CoroutineExecutionContext context)
    {
        DeploymentCatalog catalog;

        if(Catalogs.TryGetValue(uri, out catalog))
            Completed(this, new ResultCompletionEventArgs());
        else
        {
            catalog = new DeploymentCatalog(uri);
            catalog.DownloadCompleted += (s, e) =>{
                if(e.Error == null)
                {
                    Catalogs[uri] = catalog;
                    Catalog.Catalogs.Add(catalog);
                    catalog.Parts
                        .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
                        .Where(assembly => !AssemblySource.Instance.Contains(assembly))
                        .Apply(x => AssemblySource.Instance.Add(x));
                }
                else Loader.Hide().Execute(context);

                Completed(this, new ResultCompletionEventArgs {
                    Error = e.Error,
                    WasCancelled = false
                });
            };

            catalog.DownloadAsync();
        }
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
}

ケースでは、このサンプルでは、MEFを使用して、明確ではありませんでした。DeploymentCatalogシルバーライト用に作成したのまた、私たちは利用しています。あなたは本当にお持ち帰りを取得するために、MEFまたはDeploymentCatalogについて多くを知る必要はありません。ただ、実際のメモを取るDownloadCompletedイベントの我々のワイヤれ、そのハンドラでIResult.Completedイベントを発生することを確認してください。これは、非同期パターンが機能することができますものです。また、エラーをチェックし、ResultCompletionEventArgsにその沿って通過するようにしてください。そのことを言えば、ここでそのクラスは次のようになります。

public class ResultCompletionEventArgs : EventArgs
{
    public Exception Error;
    public bool WasCancelled;
}

Caliburn.Microの列挙子は、それが戻って各IResultから呼ばれるのを取得した後、これらのプロパティをチェックします。エラーまたはWasCancelledがtrueに設定されているのいずれかがある場合、我々は実行を停止します。あなたは、あなたの利点にこれを使用することができます。あなたがOpenFileDialogのためIResultを作成するとしましょう。あなたは、そのダイアログの結果を確認でき、ユーザーがそれをキャンセルした場合、イベント引数にWasCancelledセット。これを行うことにより、コードがは、Dialog.showが実行を次の場合は、ユーザーがファイルを選択している必要があることを前提としていたアクションを書くことができます。 技術のこの種類は、そのような状況でロジックを簡素化することができます。明らかに、あなたはSaveFileDialogのために同じ技術か、必要に応じて任意の確認スタイルのメッセージボックスを使用することができます。上に示したLoadCatalog実装の私のお気に入りの部分は、元の実装がCMのユーザによって書かれたということです!この素晴らしい提出ありがとうjanoveh!注意点として、私たちはCMプロジェクトのサイトに追加のものの一つは、「レシピ」セクションです。我々は今後数ヶ月でその領域にこのようなより一般的な解決策を追加しようとしています。だから、それは、フレームワークにクールなプラグインやカスタマイズをチェックするのに最適な場所になります。

あなたが行うことができますもう一つは、アプリケーションのシェルを中心に構築されたIResultの実装のシリーズを作成することです。つまり、上記の使用ShowScreen結果が何をするかです。ここでは、その実装は次のとおりです。

using System;
using System.ComponentModel.Composition;

public class ShowScreen : IResult
{
    readonly Type screenType;
    readonly string name;

    [Import]
    public IShell Shell { get; set; }

    public ShowScreen(string name)
    {
        this.name = name;
    }

    public ShowScreen(Type screenType)
    {
        this.screenType = screenType;
    }

    public void Execute(CoroutineExecutionContext context)
    {
        var screen = !string.IsNullOrEmpty(name)
            ? IoC.Get<object>(name)
            : IoC.GetInstance(screenType, null);

        Shell.ActivateItem(screen);
        Completed(this, new ResultCompletionEventArgs());
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

    public static ShowScreen Of<T>()
    {
        return new ShowScreen(typeof(T));
    }
}

これは、IResultのもう一つの重要な特徴を持ち出します。 CMが結果を実行する前に、それはあなたのコンテナプロパティを通じてで依存関係をプッシュする機会を可能にするIoC.BuildUp法を介して渡します。これは、あなたはまだ彼らがアプリケーションサービスの依存関係を取ることが可能にしながら、通常はビュー·モデル内にそれらを作成することができます。このケースでは、ISHELLに依存します。また、あなたのコンテナが注入かもしれないが、この場合、私は内部のIoC静的クラスを使用することにしました。一般的なルールとして、あなたは直接コンテナから物事を引っ張って避ける必要があります。しかし、私はそのようなShowScreen IResultとしてインフラストラクチャコードの内部で行わときに、それが許容可能であると思います。

Other Usages

アウトオブボックスのCaliburn.MicroはActionMessage経由で呼び出されたすべてのアクションのために自動的にコルーチンを実行することができます。しかし、あなたが直接コルーチン機能を利用したい場合があり時間があります。コルーチンを実行するには、静的Coroutine.BeginExecuteメソッドを使用することができます。

私はこれがIResultを用いて達成することができる何のためにいくつかの説明と創造的なアイデアを与える願っています。添付のサンプルアプリケーションをチェックアウトしてください。同様にそこにいくつかの他の興味深いものがあります。

※サンプルアプリケーションが見つからないよ・・・。