FC2ブログ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

コマンドクラスのリメイク(参)

コマンドクラスのリメイク(壱)
コマンドクラスのリメイク(弐)
の続き。長くなってしまった。
・汎用抽象クラス
 同期、非同期に依存しない共通部分を汎用クラス化。実装が分かれる部分を抽象メソッドとしている。実行処理本体以外は、そんなに違わない。
〇クラス宣言
namespace Tawamuredays.Commands {

using Tawamuredays.Data;

/// <summary>
/// コマンド用 基底クラス
/// </summary>
/// <remarks>
public abstract class CommandExBase : ICommandEx {

汎用クラスのインスタンスを作られても困る(意味ない)ので、abstractにしておく。
〇コンストラクタ
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="executeAction">実行アクション(必須)</param>
/// <param name="canExcuteAction">実行可否判定アクション</param>
/// <param name="dispatcher">ディスパッチャー</param>
public CommandExBase(Action<object> executeAction,
Func<object, bool> canExcuteAction = null,
Dispatcher dispatcher = null) {
this.ExecuteAction = executeAction;
this.CanExecuteAction = canExcuteAction;
this.Dispatcher = dispatcher ?? System.Windows.Application.Current.Dispatcher;
return;
}
コンストラクタで必須なのは、実行用アクション(executeAction)のみ。executeActionにnullが設定されたら、ArgumentNullExceptionをスローしても良いくらい。
〇要となるプロパティ
以下3つは、コマンドクラスの要であり、コンストラクタ引数を保持する。
/// <summary>実行用アクション</summary>
protected Action<object> ExecuteAction {get; private set;}

/// <summary>実行可否判定用アクション</summary>
protected Func<object, bool> CanExecuteAction {get; private set;}

/// <summary>ディスパッチャー</summary>
protected Dispatcher Dispatcher {get; set;}

Action系のプロパティは、Command内で実行する処理の要。Setterのアクセスを制限して、処理途中で切り替えさせない。
Actionって、イベントみたいに+=でメソッドを追加できるし。Setterをpublicにすると、いろいろ面倒になりそうだったので。

〇追加のプロパティ
-実行のタイミング(優先順位)を設定するためのプロパティ。
/// <summary>ディスパッチャー実行優先順位</summary>
public DispatcherPriority Priority {get; set;} = DispatcherPriority.Normal;
Dispatcher処理時に設定できたほうが良いので追加。常にNormal(最優先)が最善とは限らない。

-実行の前後に割り込ませるためのプロパティ
/// <summary>処理実行直前に呼び出されるAction</summary>
public Action<ICommandEx, object> PreviewExecuteAction {get; set;}

/// <summary>処理実行完了時に呼び出されるAction</summary>
public Action<ICommandEx, object> CompletedExecuteAction {get; set;}
処理の開始前や完了後に、実行可否値変更(CanExecuteChanged)イベントを発生させるのに便利。便利というか、必須かな。

〇ICommanEx メンバー
ICommanExインターフェースを実装するので、プロパティやメソッドは実装必須。
-NowExecuting プロパティ
/// <summary>実行中かどうか</summary>
private ConcurrentData<bool> nowExecuting_ = new ConcurrentData<bool>(false);

/// <summary>実行中かどうか</summary>
public bool NowExecuting {
get => this.nowExecuting_?.Value ?? false;
set {
if (this.nowExecuting_ == null) {
return;
}
this.nowExecuting_.Value = value;
}
}
主に非同期型コマンド用。インターフェースに持たせることで、同期非同期を意識せずにチェックしたいので、同期型でも実装させている。ConcurrentDataでスレッドセーフにアクセスできるようにしている(つもり)。

-NeedSync プロパティ
/// <summary>同期処理が必要かどうか</summary>
public bool NeedSync {get; set;} = true;
あまり目立たないけど、エラー回避のためにそこそこ重要。

-CanExecuteChangedイベントと、RaiseCanExecuteChangedメソッド。
/// <summary>実行可否変更イベント</summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// CanExecuteChanged イベントを発生させます。
/// </summary>
public void RaiseCanExecuteChanged() {
this.Dispatcher.Invoke(new Action<CommandExBase>(command => {
command?.CanExecuteChanged?.Invoke(command, EventArgs.Empty);
}), this);
return;
}
実行可否状態変更のイベントは、データバインディング的に重要なファクターとなる。意図的に発生させたくはなかったけど、画面開発するうえでは必須になってしまった。

-CanExecuteメソッド
ICommandインターフェースのメンバー。同期型だろうと、非同期型だろうと、このあたりの処理は共通なので、ここで実装しておく。
overrideできるように、virtual宣言をしている。overrideさせたくなければ、sealedしておくのもありかな。
/// <summary>
/// 実行可能かどうかを取得します。
/// </summary>
/// <param name="parameter">コマンドパラメータ</param>
/// <returns>true:実行可能、false:実行不可能</returns>
public virtual bool CanExecute(object parameter) {

if (this.NowExecuting) {
//すでに実行中。falseを返す。
return false;
}

if (this.CanExecuteAction == null) {
//判定用Func未指定なら、常にtrue
return true;
}

try {
if (this.Dispatcher.Thread == System.Threading.Thread.CurrentThread) {
//UIスレッド上にいるので、そのままCanExecuteActionを実行する。
return this.CanExecuteAction(parameter);
} else {
//UIスレッド上にいないので、Dispatcherで同期的実行を試みる。
return (bool)this.Dispatcher.Invoke(
new Func<CommandExBase, object, bool>((command, param) => {
return (command?.CanExecuteAction(param)) ?? false;
}),
this, parameter);
}

} catch (Exception) {
//本来はLoggingクラス等でエラー内容を残す。
return false;
}
}
UIスレッド上でない(非同期型コマンドが絡む)のなら、Dispatcher.Invokeをしておいた方が無難。コメントにも書いているが、例外が発生した場合はログファイルやコンソール等に出力できれば、修正するコストを短縮できる。

-Executeメソッド
NowExecutingプロパティを更新し、PreviewExecuteActionを実行、のちに実行本体を処理する。この流れは変えてほしくないので、継承クラスにはExecuteメソッドではなく、ExecuteProtectedをoveerideしてもらう。
/// <summary>
/// 実行します。
/// </summary>
/// <param name="parameter">コマンドパラメータ</param>
public void Execute(object parameter) {
//(1)実行中というフラグを立てる
this.NowExecuting = true;
//(2)処理前Action (CanExecuteChangedEvent発動?)
this.PreviewExecuteAction?.Invoke(this, parameter);
//(3)実行処理の本丸。継承クラスは、このメソッドをoverrideする。
this.ExecuteProtected(parameter);
return;
}
なお、非同期型を実装するため、完了後用のCompletedExecuteActionは、ここでは呼び出せない。
-Clearメソッド
/// <summary>
/// 内部データをクリアします。
/// </summary>
public virtual void Clear() {

if (this.CanExecuteChanged != null) {
foreach (EventHandler handler in
this.CanExecuteChanged.GetInvocationList()) {
this.CanExecuteChanged -= handler;
}
}

this.ExecuteAction = null;
this.CanExecuteChanged = null;
this.Dispatcher = null;
this.nowExecuting_ = null;
return;
}
イベントハンドラを削除し、ActionやFuncへの参照をnullにする。ActionやFuncは、基本的に他のクラスインスタンスのメソッドやプロパティへアクセスするので、nullにしておかないと、いつまでも参照が残り、いつまでもメモリが解放されない。
VS2015以降に装備されたデバッガ等で、メモリ上にあるオブジェクトの数をカウントできるけど、画面を終了しても、インスタンスの数が減らないときは、大体こいつらのせい。
経験上、メモリリークの原因は

-ActionやFunc
-ラムダ式
-イベントハンドラ

の可能性が高い。特にイベントハンドラとラムダ式の組み合わせは最悪。削除しているつもりでできていないこことが多い。

〇抽象メソッド
/// <summary>
/// コマンドを実行します。ExecuteActonを実行してください。
/// </summary>
/// <param name="parameter">コマンドパラメータ</param>
protected abstract void ExecuteProtected(object parameter);
これが、同期用、非同期用コマンドで、実装が分かれるメソッドの本丸。
続く
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。