No more Death March

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

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章で紹介されているパターン
通常のイベントハンドラを使わずにクラスをイベントに見立て、クラス間の結合を弱める。

実践ドメイン駆動設計

実践ドメイン駆動設計

偶然なのか、こちらの書籍でも第8章

自分なりに実装

まず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 obj)⇒弱参照を強参照に変換するメソッド

とりあえず今日はここまで。

WPF 型'UserControl'はダイレクトコンテンツをサポートしていません。

クラスライブラリに(WPFの)UserControlを追加するとたまに怒られることがある。

どうやら参照設定にSystem.XamlXAMLのパース用ライブラリ?)を追加していない状態でUserControlを追加すると再現するみたい。
System.Xamlを参照設定に追加してビルドすると一端はエラーは消えるが、
LabelやらTextBoxやらを追加しようとするとインテリセンスが上手く動いてくれない様子で、
コントロールを追加するとまたエラーが再発する。

Visual Studioを再起動するとエラーが収まります。

WPFってちょいちょいXAMLのパース周りで???っていう動きして混乱します。

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プロパティを設定しています。
f:id:nomoredeathmarch:20170305165515p:plain

続いて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は反応なしになるわけですね。