C# リファクタリング コレクションを返すメソッド
なんらかの処理を行ってコレクションを返すようなメソッドがあったとして、
こんな具合にパラメータがnullだったら例外を投げ返すんだったら
public IEnumerable<object> Hoge(object obj) { if (obj == null) throw new ArgumentNullException("obj"); // なにか処理してコレクションを返す。 }
こんな具合に空の配列を返してやる。
public IEnumerable<object> Hoge(object obj) { if (obj == null) new object[] { }; // なにか処理してコレクションを返す。 }
メソッドの目的にもよるのだろうけど、
例えば検索処理とかだと変なパラメータを渡したら例外を投げずに空の結果を返してやれば良い。
書いてみて思ったけど、おかしなことをやっているならやっぱり例外を投げて気づく機会を設けるべきなんだろうか・・・
C# リファクタリング 早期リターン
int型の引数が5以下だったらTrueを返すとして、
public bool Hoge(int value) { if(value < 5) { return true; }else{ return false; } }
こうだったのを
public bool Hoge(int value) { if (value < 5) return true; return false; }
こうする。
ソースコードが5行から2行へ、
ごくごくシンプルな例ですが、複雑な条件が組み合わさる場合時は、
結果が確定している時点でさっさとメソッドから抜け出すようにすると修正時のリスクが少し軽減出来る。
オブジェクト指向もといプログラムを考える。
レガシープログラマの歴が長く、オブジェクト指向は独学、
何が良いプログラムで何が悪いプログラムなのかもやもやする・・・
日々頭の中の考えがあっちいったりこっちいったりで切りがないので、
気が付いた時に文章に書いておいた方がいいかもしれない。
今日思ったこと。
プログラムを作る時、まずごくごくシンプルなインターフェースで考える。
インターフェースが決まったらインターフェースをコンポジットする実装クラスを作る。
そして単一の処理を行う実装クラスを作る。
後はユースケースに合わせてコンポジットしたものを用意する。
同一モデルに対するチェック処理や手続き的な編集処理はこういう作りにしとくと組み換えが利いて良いかもしれない。
C# ドメインイベントの実装 つづき
前回のエントリーの続きから
nomoredeathmarch.hatenablog.com
Publisherクラスのメソッドについて記述
まずSubscribeメソッドから
public static void Subscribe(ISubscriber subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); if (Subscribers.Contains(new WeakReference<ISubscriber>(subscriber))) return; Subscribers.Add(new WeakReference<ISubscriber>(subscriber)); } finally { Mutex.ReleaseMutex(); } }
・イベントの通知を予約するためのメソッド
・Mutexクラスを使って排他制御(finallyで解放を忘れずに)
・イベントの多重処理防止のため登録済みのサブスクライバだったら処理を抜ける。
・サブスクライバの弱参照をリストに追加する。
続いてUnsubscribeメソッド
public static void Unsubscribe(ISubscriber subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); Subscribers.RemoveAll(x => subscriber.Equals(ToStrong(x))); } finally { Mutex.ReleaseMutex(); } }
・イベントの通知予約を解除するメソッド。
・Subscribeメソッドと同様にMutexで排他制御
・リストからの削除は強い参照に変換して対象を比較
続いてToStrongメソッド
private static ISubscriber ToStrong(WeakReference<ISubscriber> obj) { ISubscriber ret; obj.TryGetTarget(out ret); return ret; }
・弱参照を強参照に変換して返すメソッド。
・クラス内の他のメソッド内で呼び出されるprivateなメソッド。
最後にPublishメソッド
public static void Publish(IEvent e) { try { Mutex.WaitOne(); if (e == null) throw new ArgumentNullException("e"); var list = new List<ISubscriber>(); list.AddRange(Subscribers.ConvertAll<ISubscriber>(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(); } }
・受け取ったイベントをサブスクライバに通知する。
・ここでもやっぱりMutexで排他制御
・弱参照のリストを強参照に変換、変換出来なった要素やCanHandleメソッドでFalseが返る要素をリストから除外
・ForEachメソッドで各要素のHandleメソッドにイベントを渡す。
・イベントの通知が終わったらGC回収済みのサブスクライバをリストから除外する。
注意事項
1.排他制御
今回はMutexクラスを使って各Publicメソッドの先頭で排他制御を掛けた。
マルチスレッド周りのプログラムは正直あまり理解していないので辛うじて自分が分かる方法です・・・
2.メモリリーク対策
静的なリストはRemoveなりClearメソッドで中身を消さないと、アプリケーションが動いている限り参照が残り続け、メモリリークを起こす。クライアントコード側でDisposeを実装し、確実にリストから排除する仕組みになっていれば問題ないが、実装漏れが起こるリスクを考えるとPublisherクラス内を弱参照を駆使して実装する方が安全と言える。
3.購読停止の管理
サブスクライバがGCに回収されない限りイベントは通知される。購読が不要になった時点でUnsubscribeメソッドを呼び出す。
C# ドメインイベントの実装
こちらの書籍の第8章で紹介されているパターン
通常のイベントハンドラを使わずにクラスをイベントに見立て、クラス間の結合を弱める。
- 作者: ヴァーン・ヴァーノン
- 出版社/メーカー: 翔泳社
- 発売日: 2015/03/19
- メディア: Kindle版
- この商品を含むブログ (2件) を見る
偶然なのか、こちらの書籍でも第8章
.NETのエンタープライズアプリケーションアーキテクチャ第2版 .NETを例にしたアプリケーション設計原則
- 作者: ディノエスポシト,アンドレアサルタレロ
- 出版社/メーカー: 日経BP社
- 発売日: 2015/06/18
- メディア: Kindle版
- この商品を含むブログを見る
自分なりに実装
まずIEventインターフェース
namespace Nmdm.Events { public interface IEvent { } }
これはイベントである目印をつけるためのマーカー
続いて、ISubscriberインターフェース
namespace Nmdm.Events { public interface ISubscriber { void Handle(IEvent value); bool CanHandle(IEvent value); } }
こちらはIEventを受け取って処理を行うためのインターフェース
HandleメソッドはIEventを引数に処理を行うメソッド、
CanHandleメソッドはIEventを処理できるかどうかを後述のPublisherに伝えるためのメソッド
続いてPublisher、そのままだと長いのでメソッドの中身は後述
using System; using System.Collections.Generic; using System.Threading; namespace Nmdm.Events { public static class Publisher { private static Mutex Mutex { get; } = new Mutex(); private static List<WeakReference<ISubscriber>> Subscribers { get; } = new List<WeakReference<ISubscriber>>(); public static void Publish(IEvent e) { //省略 } public static void Subscribe(ISubscriber subscriber) { //省略 } public static void Unsubscribe(ISubscriber subscriber) { //省略 } private static ISubscriber ToStrong(WeakReference<ISubscriber> obj) { //省略 } } }
Publisherクラスは受け取ったIEventをISubscriberに通知するのが役割。
クラスメンバーについて補足:
・Mutex⇒スレッドセーフにするための排他制御用
・Subscribers⇒ISubscriberの弱参照リスト、IEventの通知先保存用
・Publish(IEvent e)⇒Subscribersに受け取ったイベントを通知する。
・Subscribe(ISubscriber subscriber)⇒受け取ったサブスクライバを弱参照リストに追加する。
・Unsubscriber(ISubscriber subscriber)⇒受け取ったサブスクライバを弱参照リストから削除する。
・ToStrong(WeakReference
とりあえず今日はここまで。
WPF 型'UserControl'はダイレクトコンテンツをサポートしていません。
WPF Bind Modeの動きを確認
BindingクラスのBindingModeについて、改めて動きを確認してみました。
まずビュー側
<Window x:Class="WhatIsBindingMode.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WhatIsBindingMode" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="Label"> <Setter Property="Height" Value="24"/> <Setter Property="HorizontalAlignment" Value="Right"/> </Style> <Style TargetType="TextBox"> <Setter Property="Height" Value="24"/> </Style> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0"> <Label Content="Mode = Default:"/> <Label Content="Mode = OneTime:"/> <Label Content="Mode = OneWay:"/> <Label Content="Mode = OneWayToSource:"/> <Label Content="Mode = TwoWay:"/> </StackPanel> <StackPanel Grid.Column="1"> <TextBox Text="{Binding Path=ModeDefault ,Mode=Default}"/> <TextBox Text="{Binding Path=ModeOneTime ,Mode=OneTime}"/> <TextBox Text="{Binding Path=ModeOneWay ,Mode=OneWay}"/> <TextBox Text="{Binding Path=ModeOneWayToSource ,Mode=OneWayToSource}"/> <TextBox Text="{Binding Path=ModeTwoWay ,Mode=TwoWay}"/> </StackPanel> </Grid> </Window>
このViewの実行画像が以下の通り、各テキストボックスに異なるModeプロパティを設定しています。
続いてViewModel側、Modeプロパティの値と対応するプロパティを持っています。
呼び出しのタイミングを確認するため、プロパティ内のステップの先頭とプロパティの値が変更された場合にメッセージ出力をします。
using System.Diagnostics; using System.ComponentModel; namespace WhatIsBindingMode { public sealed class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged == null) return; this.PropertyChanged( this, new PropertyChangedEventArgs(propertyName) ); var message = string.Format("Property {0} Changed", propertyName); Debug.WriteLine(message); } private string _modeDefault = string.Empty; public string ModeDefault { get { Debug.WriteLine("ModeDefault.get Called"); return this._modeDefault; } set { Debug.WriteLine("ModeDefault.set Called"); if (this._modeDefault == value) return; this._modeDefault = value; this.RaisePropertyChanged(nameof(this.ModeDefault)); } } private string _modeOneTime = string.Empty; public string ModeOneTime { get { Debug.WriteLine("ModeOneTime.get Called"); return this._modeOneTime; } set { Debug.WriteLine("ModeOneTime.set Called"); if (this._modeOneTime == value) return; this._modeOneTime = value; this.RaisePropertyChanged(nameof(this.ModeOneTime)); } } private string _modeOneWay = string.Empty; public string ModeOneWay { get { Debug.WriteLine("ModeOneWay.get Called"); return this._modeOneWay; } set { Debug.WriteLine("ModeOneWay.set Called"); if (this._modeOneWay == value) return; this._modeOneWay = value; this.RaisePropertyChanged(nameof(this.ModeOneWay)); } } private string _modeOneWayToSource = string.Empty; public string ModeOneWayToSource { get { Debug.WriteLine("ModeOneWayToSource.get Called"); return this._modeOneWayToSource; } set { Debug.WriteLine("ModeOneWayToSource.set Called"); if (this._modeOneWayToSource == value) return; this._modeOneWayToSource = value; this.RaisePropertyChanged(nameof(this.ModeOneWayToSource)); } } private string _modeTwoWay = string.Empty; public string ModeTwoWay { get { Debug.WriteLine("ModeTwoWay.get Called"); return this._modeTwoWay; } set { Debug.WriteLine("ModeTwoWay.set Called"); if (this._modeTwoWay == value) return; this._modeTwoWay = value; this.RaisePropertyChanged(nameof(this.ModeTwoWay)); } } } }
このViewModelをMainWindowのコードビハインドでDataContextプロパティに渡しています。
using System.Windows; namespace WhatIsBindingMode { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new ViewModel(); } } }
これで準備完了。
画面起動時
まずは画面を起動したときのメッセージ。
ModeDefault.get Called ModeOneTime.get Called ModeOneWay.get Called ModeOneWayToSource.set Called ModeTwoWay.get Called
OneWayToSource以外はViewModel側のgetterが呼び出され、OneWayToSourceのみsetterが呼び出される。
OneWayToSourceはView⇒ViewModelの一方向のバインドなので、ViewModelの内容を問わずViewの値を更新するわけね。
DefaultとTwoWay
DefaultでバインドしているTextBoxに「hoge」と入力してタブ移動
ModeDefault.set Called Property ModeDefault Changed ModeDefault.get Called
TextBoxの既定値はTwoWayなのでTwoWayでも同じ
ModeTwoWay.set Called Property ModeTwoWay Changed ModeTwoWay.get Called
ここで少しひっかかる、PropertyChangedイベンドが発生した後にProperty ~ Changedってメッセージを出力しているのに、
~.get Calledのメッセージが出力されるのはそれよりさらに後、
イベントが発生したタイミングでGetterから値を取得するなら~.get Calledが先に出力されるのでは???
うーん・・・
OneTime
OneTimeは一度だけViewModelから値を取得するので画面で操作しても反応なし。
OneWay
OneWayはViewModelからViewへの一方的な連動なので画面で操作しても反応なし。
OneWayToSource
OneWayToSourceはOneWayの逆方向でViewからViewModelへの一方的な連動なので、
Setterのみ呼ばれ、Getterは呼ばれない。
ModeOneWayToSource.set Called Property ModeOneWayToSource Changed
ViewModelの更新
面倒になったのでソースは出さないけど、
ViewModelのSetterで直接値を更新すると、
Default、TwoWay、OneWayが指定されているプロパティがViewに反映され、
OneTime、OneWayToSourceは反応なしになるわけですね。