WPF MVVM Infrastructure Livet

RIAアーキテクチャ研究会
第4回セミナー
尾上 雅則
自己紹介

尾上 雅則 (おのうえ まさのり)
昭和58年生。フリーランス
Blog – the sea of fertility http://ugaya40.net
Twitter - @ugaya40
Facebook – http://facebook.com/ugaya40
C#er/MVVMer

Microsot MVP for Visual C#(2012/7~)





Agenda
 WPF
MVVM開発の悩みどころ
 Livetの概要
 Livetのライフサイクル管理
 冗長で面倒なコードを倒す
 Livetのメッセージング機能
 Livetのその他の機能
 まとめ - Livetの未来
WPF MVVM開発の悩み処
MVVM という考え方 – 概要
 Model
– View – ViewModelパターン
 WPFをはじめとするXAML系プラットフォームのため
に生まれたMVC系パターン
 PresentationDomainSeparationを目的とする
 現在はXAML系のみならずAndroidやブラウザ
Javascriptの世界でも利用される
 MVCなどと比べて優劣があるものではなく、適する
プラットフォームと適さないプラットフォームがある
のみであることに注意
MVVM という考え方 – 各責務




Viewの責務

XAMLとXAML関連コードで賄える事(表示など)

XAMLのための状態ストア

ViewとViewModel以外の部分(XAMLプラットフォームが関連
ない部分)
ViewModelの責務
Modelの責務
詳細はこちら
http://ugaya40.net/architecture/20120804wankuma.html
問題① – メモリリーク
MVVMに限らず、Observaerパターンを使用するス
テートフルなリッチクライアントのための責務分割型
設計パターンではWebシステムでは発生しにくいイベ
ント関連のメモリリークが非常に発生しやすい。
 .NETにおけるイベントの仕組みと参照の関係
 責務分割した際のライフサイクル
 C#でのイベントハンドリングと解除
イベントの仕組み -
問題①
||
.NETではイベント発行側がイベント受信側の参照を保
持している。つまりイベント受信側はイベントの購読
を解除してからじゃないと勝手に消滅できない。
イベントの購読を解除せずに、受信側オブジェクトを
破棄したつもりだと実際には受信側オブジェクトは消
滅せずメモリリークとなる。
責務分割型のライフサイクル – 問題①
 ViewとViewModelは基本データバインド(内部で
WeakEventが使用されメモリリークしない)
 Model(ViewとViewModel以外の部分)はアプリケー
ションの本質にかかわる部分なのでずっと消滅しない
ことが多い。つまりViewModelより長生き。
責務分割型のライフサイクル – 問題①
<
長生き
イベント監視
参照保持
イベントハンドラの解除をかなり正確にやらないとリ
スキーなのはわかりますよね?
C#でのイベント - 問題①
C#でのイベントハンドリング、どう書きますか?
では解除はこうですか?
これでは解除できません。残念ながらラムダで直接イベント
ハンドラを書いた時点でイベントを解除する手段はなくなり
ます。
C#でのイベント - 問題①
イベントハンドリングとその解除を適切に行うにはこ
うします。
しかしイベントハンドラの登録と解除を同じメソッド
内で行うことは稀有です。つまり・・
C#でのイベント - 問題①
ハンドラとイベント発行オブジェクトでフィールド管理が地
獄に!!!
問題① – メモリリーク
まとめ
 MVVMパターンではイベント受信側オブジェクトの方
が基本的に寿命が短く、メモリリークのリスクが大き
い。
 そのため適切にイベントハンドラの登録と解除を管理
する必要がある。
 C#でのイベントハンドラの登録と解除が複数メソッ
ドにまたがる場合(その方が多い)、クラスフィールド
でイベントハンドリング関連オブジェクトが増えやす
くつらい。
問題②–バインドできないプロパティ
 MVVMパターンのViewModelはViewの状態ストアで
す。XAMLはバインディングファーストなDSLである
ため、Viewは基本的にViewModelのプロパティをバ
インドで表示する形でレンダリングされます。
 ViewとViewModel間の通信は極力データバインディ
ングで統一されるほど問題が発生しにくくなっていま
す。


データバインディングを使用するとViewはViewModel
の変更通知イベントを監視しますが、データバインディ
ング機構内部ではWeakEvent(弱いイベント)という機
構が使用されているため、メモリリークが発生しません。
単純に責務感通信方法が統一されるほど管理は容易
問題②–バインドできないプロパティ
バインドできない!
 依存関係プロパティとして用意されているもの以外は
バインドできません。それが結構多いんです・・。
問題③–冗長なコード1
バインドするために必須な
変更通知プロパティ。
冗長なコードの代表格です
ね。面倒です。
(ViewModel & Model)
問題③–冗長なコード2
 操作をバインドするため
のICommand実装。こ
れも冗長です。
(ViewModel)
WPF MVVM開発の悩み処 – まとめ
①
メモリリークが発生しやすい & きちんとした管理が難し
いのでイライラする
②
バインドできないプロパティにイライラする
③
冗長なコードにイライラする
→
要はイライラする。
※一番上のもの以外はMVVMのせいではなくWPF(というか
XAML系全般)自体の問題であることに注意
Livetはこれらに対応するソリューションです。
Livetの概要
Livetとは?
 WPF4のための「国産」MVVMインフラストラクチャ
 ライセンスはzlib/libpng


通常の使用であれば、クレジットなど必要ない
ソース改変使用する場合はクレジットの明記が必要


Project Home
 http://ugaya40.net/Livet
GitHub

https://github.com/ugaya40/Livet
Livetとは?
 来月(2012/10)で開発開始から2年
 採用実績は企業・個人開発者ともに多数
 おそらくは日本一のC#erたちが集うTLにて多くの方
からフィードバックをいただき、議論を積み重ねて
作ってきました
 MVVMの概念そのものを突き詰めた結果のシナリオを
ベースに機能を構築しており、ほかのMVVMライブラ
リが未着手な問題の多くに着手できています
Livetのインストール
 Visual
Studio 2010/2012なら拡張機能マネージャ
から一発導入
 Visual Studio 2010 Express C#/Visual Basic ある
いはVisual Studio 2012 Express for Windows
DesktopならSetup.exeから一発導入
 ライブラリだけならNugetからも導入可能です
Livetの構成
 Visual
Studio2010/2012/Express各環境用、
C#/VisualBasic双方用のプロジェクトテンプレー
ト・アイテムテンプレート・コードスニペットなどが
含まれます。
Livetのライフサイクル管理
Livetのライフサイクル管理
 Livet0.99系までは、Model⇔ViewModel間のライフ
サイクル管理にWeakEventを使用してメモリリーク
を防いでいました。
 しかし、WeakEventはCPUが回りすぎるという
フィードバックをいくつかいただいたため、また
ViewModelとModelの間はView⇔ViewModelの間ほ
どライフサイクル管理が難しくはないため、現在の
Livet1.0系ではすべて手動管理する形になっています。
※View⇔ViewModel間は極力データバインディングによ
る通信を行う事で、WeakEventが自動的に使用されます
Livetのライフサイクル管理
 メモリリークの問題、このコードをLivetはどう変え
るのでしょうか?
Livetのライフサイクル管理
 ViewModelではこうなります。ともにLivetの機能で
あるPropertyChangedEventListenerと
CompositeDisposableが鍵です。
PropertyChangedEventListener
 PropertyChangedEventListenerとは、
PropertyChangedイベント購読状態をIDisposable
として扱うための機能です。Disposeによってイベン
ト購読が解除されます。
 また、普通にイベントハン
ドラでは変更通知が来たプ
ロパティ名ごとにこうなり
がちなところが・・
PropertyChangedEventListener
 あるいはコレクション初期化子を用いて・・
 こう書けたりします。
LivetCompositeDisposable
 LivetのViewModel型には最初からメンバとし
て定義されているIDisposableのコレクション
です。CompositeDisposableをDisposeするこ
とでコレクションメンバのIDisposableもすべ
てDisposeされます。
 LivetのViewModel(IDisposable)型では
ViewModelがDisposeされる際に
CompositeDisposableがDisposeされます。
LivetCompositeDisposable
 つまりこう記述するだけで、listenerのイベン
ト購読もViewModel破棄時にすべてDisposeさ
れるわけです。
ViewModelのDisposeは誰が呼ぶのか?

LivetのWindowテンプレートではWindowを閉じる
際に、DataContextがIDisposableであれば
Disposeを行うビヘイビアが最初から記述されてい
ます。
 WPFのViewModelのオブジェクト階層は結局常
にWindowのDataContextがルートにいます。
メンバに子ViewModelが存在するViewModelも、
その子ViewModelを自身の
CompositeDisposableに放り込むことで簡単に
ハンドラの解除が管理できるわけです。
ViewModelのDisposeは誰が呼ぶのか?
 Windowが閉じられるとす
べてのイベントハンドラ
や破棄可能なリソースが
連鎖的に破棄されます
Livet - ViewModelのライフサイクル管理
 LivetではPropertyChangedEventListener以外にも、
CollectonChangedEventListener、ユーザー定義イ
ベントに対応するための汎用クラスEventListenerク
ラスが定義されています。
 LivetのViewModelでは用意されている
CompositeDisposableや各種EventListenerを使用し
てViewModelのライフサイクル管理が容易に行える
ようになっています。
ViewとViewModelのライフサイクル管理
 データバインドでの通信はWeakEvent機構が使用さ
れ、メモリリークは発生しえませんが、データバイン
ドでは処理できないイベントが残ります。
 次章で説明するメッセージング機構を使えば、
ViewModelからのイベントをデータバインディング
で扱えますが、ビヘイビア・トリガー・アクションな
どの知識も必要ですし、そういったコードビハインド
に依存しない実装はView抽象化要件がなければ意義
は薄いものです。
ViewとViewModelのライフサイクル管理
 Model⇔ViewModel間と異なりView⇔ViewModel間
のハンドラの管理は難易度が高いため(Viewの生成消
滅の頻繁さ・WPF自体バグ起因・部品コンポーネン
ト化による複雑さ)自分でハンドラの管理をしっかり
できれば一番ですが、不安な場合は
LivetWeakEventListenerを使用することができます。
 LivetWeakEventListenerは汎用WeakEvent機構で
す。.NET標準のものより手軽に、しかもはるかに高
速に(実測4~5倍)WeakEvent機構を使用できます。
LivetWeakEventListener
冗長で面倒なコードを倒す
コードスニペット - デモ

Livetでは変更通知プロパティ・ICommand2種のコード
スニペットが用意されています。


lprop/lpropn


それぞれ.NET4/4.5用の変更通知プロパティ
lvcom/lvcomn


ReSharper7のINotifyPropertyChangedサポートにも対応し
ています。
引数を取らないICommand実装クラスViewModelCommand
のフル・CanExecute無し版
llcom/llcomn

引数を取るICommand実装クラスListenerCommandのフ
ル・CanExecute無し版
メソッド直接バインディング - デモ
 Livetを使用すると実はICommandを全く使わずに
ViewModelからViewに操作を公開することも可能で
す。
 LivetのView機能はすべてICommand無しのメソッ
ド直接バインディングに対応しています
 内部ではリフレクション・式木・Taskなどをフル活
用したメソッドキャッシュ機構があり、またそれらメ
ソッドキャッシュ生成機構はMethodBinderというク
ラスにまとめてあり、開発者が自身のクラスに簡単に
メソッド直接バインディングの仕組みを追加すること
もできます。
メソッド直接バインディング
 LivetCallMethodActionはBlend
SDKの
CallMethodAcitonより高速で、かつ引数をとること
もできます。
 後述するメッセージング機構でもメソッド直接バイン
ディングは使用できるので後でまた確認することにし
ます。
Livetのメッセージング機能
メッセージング機構とは?
 ViewModelからViewに操作指示メッセージを送るた
めの機構です。
 ViewModelから既定のメンバであるMessengerを使
用してViewにメッセージを送信します。
 現在のLivetでは、XAML上に記述する
InteractionMessageTriggerでMessengerからの
メッセージを受け、対応する処理となるアクションを
実行します。
 View抽象化要件がなければ、しいてメッセージング
機構に頼る必要はありません!
2種類のメッセージ対応アクションの起動-デモ
 ViewModelからInteractionMessageTriggerを介し
ての起動

ViewModelのMessenger +
InteractionMessageTriggerによるアクションの起動
 Viewから直接の起動

EventTriggerなどの任意のトリガーによるアクション
の起動
Livetのメッセージング機能まとめ
 ご覧のとおり、Livetのメッセージ機能はすべてメ
ソッド直接バインディングが使用でき、かつView起
点・ViewModel起点でアクションを起動できます。
 メッセージング機構はイベントをデータバインディン
グで処理するような機構であり、その内部ではデータ
バインディングと同じくWeakEventが使われていま
す。
 View抽象化要件がないシナリオでは特にこだわる必
要はありません。
そのほかのLivet機能
ViewModelHelper.
CreateReadOnlyDispatcherCollection



Modelの変更通知コレクションに自動的に連動する
ViewModelのコレクションを作製します。
戻りであるReadOnlyDispatcherCollectionはIDisposable
であり、DisposeすることでModelコレクションとの連動
が解除され、自身の各要素がIDisposableである場合すべ
てDisposeします。
簡単にModelコレクションからViewModelのコレクション
を生成でき、CompositeDisposableと組み合わせること
で管理も容易です。
バインドできないプロパティ対策

Livetではバインドできないプロパティの単一方向バインドを可
能にするビヘイビアとアクションが大量に含まれています。

バインドシステムの制約から、2種類ともつけて双方向と
いうやり方はできません。
ビヘイビア・アクション・トリガーなど
 LivetDataTrigger

Blend SDKのそれと違い、初期値に対応した
DataTrigger
 WindowCloseCancelBehavior

Windowのクローズ可否処理をViewModelなどバインド
先に委譲できるビヘイビア
 SetFocusAction

アタッチしたコントロールにフォーカスを移動する
など
その他
 ObservableSyncronizedCollection

スレッドセーフなObservableCollection
 汎用EnumToBooleanConverter


Enumとboolの相互変換を行いIValueConveterの実相
の手間を削減する
アタッチしたコントロールにフォーカスを移動する
など
Livetの未来 - まとめ
Livetの今後の予定
 Livet英語化プロジェクト
 コードビハインド実装の補完

より簡潔なViewModelイベントのコードビハインドで
のハンドリングなど
 バインドできないプロパティ対策の高度化

バインドできないプロパティの中からシナリオベースで
よく使われる機能を双方向バインド可能にする
 (すでにTextBoxとPasswordBoxはある)
Livet - まとめ
 LivetはMVVMについて適切に理解していれば適切に
理解しているほどその機能がいかせるライブラリです。
 ほかのMVVMライブラリと異なりViewModel単体で
はなく全体を見たライブラリになっています。
 プロジェクトテンプレート・スニペット・ハンドラ管
理・メソッド直接バインディングなどの機能をつかっ
ていただければ2度とほかのMVVMライブラリには戻
れないものになっていると思います。
ご清聴ありがとうございまいた!