No more Death March

あるSEのチラシの裏 C# WPF

WPF パフォーマンス分析ツールを入れてみる。

WPFのチューニングが全然捗らないので分析ツールを導入してみる。
バインディングはほんと便利だと思うけど、こういうツールなしにチューニングするのはきついですね・・・

とりあえずこの記事で目についたWPF Performance Suiteというのを入れてみよう。

WPFアプリケーション・チューニング − @IT

Windows SDKというやつの中に入っているのか・・・
そもそもWindows SDKってなに???
というわけでWikiで確認

Microsoft Windows SDK - Wikipedia

ええと、Microsoftが公開しているWindowsのアプリ開発者向けの補助ツール集って位置づけでいいのかな?

Visual Studioインストール時に選択すれば一緒に入るみたいだけど自分のPCは入れてない様子だったので
Microsoftのホームページからダウンロードしてインストール

Windows 10 SDK – Windows アプリ開発

既定のままインストールすると「C:\Program Files (x86)\Windows Kits」というフォルダが出来るので、
WPF Performance Suiteの実行ファイル「WpfPerf.exe」を探す。

、、、
、、、
うん、見つからない。

とりあえずMSDNを読んでみる

WPF Performance Suite

記事自体が古い・・・

ぐぐってぐぐっていきついた先が↓

Windows SDKに含まれなくなったツール - kkamegawa's weblog

あれ・・・WpfPerfってもうWindows Sdkに入っていない・・??

というか・・・


Visual Studio 2015は診断ツールがその変わりってことかな・・・

アプリケーションのタイムライン

なんかすごい時間を無駄にしてしまった・・・

なんか2015の診断ツールはCPUとかメモリとか勝手にリアルタイムで表示されて便利だなーくらいの認識だったのですが、
一応WPFアプリケーションのパフォーマンス診断にも使えるみたいですね・・・・

ええと・・

簡単にまとめると
・メニューの「デバッグ」で診断ツール表示させて「CPU使用率」タブをクリック
・「CPUプロファイリング」をクリックすると情報収集のオンオフを切り替えれる
・アプリケーションをデバック起動する。
デバッグ実行を一時停止すると「CPU使用率」タブ内にメソッド毎のCPU使用時間やCPU使用率が出てくるよ。

最初からこれ見れば良かった・・・????

WPF 実行環境によっては画面がぼやける・・・

WPFではまったのでメモします。

開発用マシンとは異なるOS、ハードで実行すると画面がぼやけてしまった。
アンチエイリアス処理の結果かなぁとは思ったけどぼやけかたが余りに酷い。

なにかないかと試してみたのがこちら↓
FrameworkElement.UseLayoutRounding プロパティ (System.Windows)

UIのルート要素でtrue(既定はfalse)を指定すると実用に耐えるくらい改善されました。

二つの環境を見比べるとアスペクト比が若干崩れていますがとりあえず我慢・・・
WPFってはまりどころがたくさんあって心が折れそう・・・

インターフェースを考える(6)IEvent

以前書いたドメインイベントの実装について、
インターフェースの側から考える。

nomoredeathmarch.hatenablog.com
nomoredeathmarch.hatenablog.com

まず作成したインターフェースについて復習

まず、イベントを表すクラスのインターフェースIEvent

namespace Nmdm.Events
{
    public interface IEvent
    {
    }
}

あくまで印をつけるためのマーカー

続いてそのインターフェースを受け取るIHandler

namespace Nmdm.Events
{
    public interface IHandler
    {
        void Handle(IEvent value);
        bool CanHandle(IEvent value);
    }
}

こちらはパブリッシャーからの受信機構となるメソッド。

でここからが今回の本題、
Publisherクラスをstaticで実装したけども、これもインターフェースで抽象化してみる。

以下がインターフェースを抜き出したIPublisher

namespace Nmdm.Events
{
    public interface IPublisher
    {
        void Publish(IEvent e);
        void Subscribe(IHandler handler);
        void Unsubscribe(IHandler handler);
    }
}

staticじゃなくなったことで単体テストがしやすくなったけど、
半面、どこからでもイベント通知、購読することが出来なくなったわけだ。

ただ、staticクラスのように振舞わせたければシングルトンなりこのクラスをキャッシュするためだけのスタティッククラスを作れば良いわけだし、
それであれば通常のクラスとして設計すれば良いとも思える。

これを以前作ったPublisherクラスに実装させる。

using System;
using System.Collections.Generic;
using System.Threading;

namespace Nmdm.Events
{
    public class Publisher:IPublisher
    {
        private static Mutex Mutex { get; } = new Mutex();
        private static List<WeakReference<IHandler>> Subscribers { get; } 
            = new List<WeakReference<IHandler>>();
        public void Publish(IEvent e)
        {
            try
            {
                Mutex.WaitOne();
                if (e == null) throw new ArgumentNullException("e");
                var list = new List<IHandler>();
                list.AddRange(Subscribers.ConvertAll<IHandler>(x => ToStrong(x)));
                list.RemoveAll(x => x == null || !x.CanHandle(e));
                list.ForEach(x => x.Handle(e));
                Subscribers.RemoveAll(x => ToStrong(x) == null);
            }
            finally
            {
                Mutex.ReleaseMutex();
            }
        }

        private static IHandler ToStrong(WeakReference<IHandler> obj)
        {
            IHandler ret;
            obj.TryGetTarget(out ret);
            return ret;
        }

        public void Subscribe(IHandler subscriber)
        {
            try
            {
                Mutex.WaitOne();
                if (subscriber == null) throw new ArgumentNullException("subscriber");
                if (Subscribers.Contains(new WeakReference<IHandler>(subscriber))) return;
                Subscribers.Add(new WeakReference<IHandler>(subscriber));
            }
            finally
            {
                Mutex.ReleaseMutex();
            }

        }

        public void Unsubscribe(IHandler subscriber)
        {
            try
            {
                Mutex.WaitOne();
                if (subscriber == null) throw new ArgumentNullException("subscriber");
                Subscribers.RemoveAll(x => subscriber.Equals(ToStrong(x)));
            }
            finally
            {
                Mutex.ReleaseMutex();
            }

        }
    }
}

インターフェースを考える(5)IAction その2

前回に続きIActionについて考える。
IPredicateやITaskと同じように組み合わせ用にCompositeActionクラスを作る。

using System;
using System.Collections.Generic;

namespace Nmdm.Actions
{
    public sealed class CompositeAction<TContext> : IAction<TContext>
    {
        public CompositeAction(IEnumerable<IAction<TContext>> actions)
        {
            if (actions == null) throw new ArgumentNullException("actions");
            this.Actions = actions;
        }

        private IEnumerable<IAction<TContext>> Actions { get; }

        public void Do(TContext context)
        {
            foreach(var obj in this.Actions)
            {
                obj.Do(context);
            }
        }
    }
}

IPredicateの時も気になったけど、このクラスを使う時にList型の変数を使ってIActionをコレクションに入れてからコンストラクタを呼び出すのかって考えるといまいちな感じがする。
かといって、クラスは基本不変にしたい・・・・

ということで↓へ改造

using System;
using System.Collections.Generic;

namespace Nmdm.Actions
{
    public sealed class CompositeAction<TContext> : IAction<TContext>
    {
        public CompositeAction()
        {
            this.Actions = new IAction<TContext>[] { };
        }

        private CompositeAction(IEnumerable<IAction<TContext>> actions)
        {
            if (actions == null) throw new ArgumentNullException("actions");
            this.Actions = actions;
        }

        private IEnumerable<IAction<TContext>> Actions { get; }

        public CompositeAction<TContext> Add(IAction<TContext> action)
        {
            if (action == null) throw new ArgumentNullException("actions");
            var actions = new List<IAction<TContext>>(this.Actions);
            actions.Add(action);
            return new CompositeAction<TContext>(actions);
        }

        public void Do(TContext context)
        {
            foreach(var obj in this.Actions)
            {
                obj.Do(context);
            }
        }
    }
}

コンストラクタをプライベートにしてAddメソッドを追加、メソッドの戻り値でprivateなコンストラクタで生成した結果を返して不変かつ流暢なインターフェースに。
このクラス自体がIActionを実装しているからAddRangeメソッドはいらないと思う。
ついでにIActionの受け口をAddメソッドだけにしたから個々の要素に対してもnullチェックが出来ている。
元のソースと比べると複雑さが増しているけどIAction以外への依存はなく変更の可能性が低いから良いかと思います。


==2017.03.14追記===
やっぱり改造やめ、読みにくいから、というならまだしも、
書きにくいから、という理由でメソッドを増やすのに少し抵抗を感じる。

インターフェースを考える(4)IAction

C#実践開発手法よりIActionインターフェースです。

namespace Nmdm.Actions
{
    public interface IAction<TContext>
    {
        void Do(TContext context);
    }
}

単一の引数を受け取る戻り値の無いインターフェースで、
引数は型パラメータで指定出来る・・・と

使いどころはどこなんだろう。
こないだ書いたITaskに引数が一つ付いただけなわけですが・・・
nomoredeathmarch.hatenablog.com

手段と目的がごっちゃになってるけど何かスマートな使い方は出来ないものか・・・

たとえば、ドメインイベントのハンドラにイベントの具体的な処理を着脱できるようにするとか?
まずIHandlerインターフェス(前の記事ではISubscriberだったけどC#実践開発手法の表記に合わせた。)

namespace Nmdm.Events
{
    public interface IHandler
    {
        void Handle(IEvent value);
        bool CanHandle(IEvent value);
    }
}

こいつの実装クラスとして、着脱可能なアクションをコンストラクタで受け取るHandlerクラス

using System;
using Nmdm.Actions;

namespace Nmdm.Events
{
    public sealed class Handler<T> : IHandler 
        where T:IEvent
    {
        public Handler(IAction<T> action)
        {
            if (action == null) throw new ArgumentNullException("action");
            this.Action = action;
        }

        private IAction<T> Action { get; }

        public bool CanHandle(IEvent value)
        {
            if (value == null) return false;
            return value is T;
        }

        public void Handle(IEvent value)
        {
            if (!this.CanHandle(value)) return;
            this.Action.Do((T)value);
        }
    }
}

コンストラクタでIAction(制約でTはIEventとする。)を受け取ってprivate変数へ。
あとはHandlerクラスがドメインイベントの通知を受け取る度にIActionが実行されると。

ドメインイベントとは違うけど、MVVMのメッセージ通知とかに使えるかな?
イベントでメッセージの内容を受け取って、アクション側でメッセージ表示の具体的な方法を実装する。
最初は標準のメッセージボックスを表示してたけど、そのうちカスタマイズしたメッセージボックスにすげ変えると・・・

パブリッシャに予約してなければ単体テストもそのままで良さそう。
イベント通知される場面なら↓みたいなIActionのモックと入れ替えても良いのか。

namespace Nmdm.Actions
{
    public sealed class ActionMock<T> : IAction<T>
    {
        public void Do(T context)
        {
        }
    }
}

「イベントの通知を受け取る」っていう仕事と「イベントの情報を読み込んで処理を実行する。」っていう仕事を分離して実装出来ますね。

WPF IValueConverter小メモ

DateTime向けのコンバーターでDateTime以外の入力があったら例外投げていたけど、
どうもnullで飛んでくるタイミングがあって例外発生してたもよう。

変換に失敗したら例外投げるより既定値で渡した方が良い?

そのうち検証しよう。