読者です 読者をやめる 読者になる 読者になる

XORveR.com の日記

XORveR.com の公式ブログです。

弱いイベントパターンに挫折したのでむしゃくしゃしてやった。とりあえず後悔していない。

DotNet wpf

他の記事との関連

心療医の処方薬でふらふらしてやった。風呂はいって寝る。」に機能追加バージョンがあります。
機能追加バージョンでは DisposeOne() メソッドで、イベントソースのインスタンス単位で購読を解除できます。

まえがき

弱いイベントパターンについてはさておき。

イベントハンドラにリスナーを登録したのはいいけれど、解除し忘れるとてきめんにメモリリークの原因となります。
弱参照でよしなに解放してくれないかな?
という浅知恵は、タイトルにあるように見事に挫折しました*1ので、さてどーするかなと。
と思いついた*2のが以下の方法。

要は、並べて書いてあればミスしないだろう!という目論見です。

登録を管理するクラスの内容

まず、以下の ListenerManager.cs を作ります。

using System;
using System.Collections.Generic;

namespace XORveR.Util {
    /// <summary>
    /// イベントリスナーの解放漏れの対策。
    /// </summary>
    public class ListenerManager : IDisposable {
        private List<Action> registered = new List<Action>();

        /// <summary>
        /// イベント購読の登録と解除を設定します。
        /// </summary>
        /// <param name="addAction">イベント購読を設定するアクション。</param>
        /// <param name="removActione">イベント購読を解除するアクション。</param>
        public void Register(Action addAction, Action removActione) {
            lock (registered) {
                addAction();
                registered.Add(removActione);
            }
        }

        #region IDisposable Support
        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing) {
            if (!disposedValue) {
                disposedValue = true;

                if (disposing) {
                    foreach (Action removeAction in registered) {
                        removeAction();
                    }
                    registered.Clear();
                    registered = null;
                }

                //disposedValue = true;
            }
        }

        public void Dispose() {
            Dispose(true);
        }
        #endregion
    }
}

ListenerManager を生成します。

ListenerManagerを、イベントを購読する側のインスタンスに変数として作って使います。

        ListenerManager listeners = new ListenerManager();

イベントハンドラへ購読の登録をします。

購読登録のそれぞれで、以下のように書いていきます。

            listeners.Register(
                () => { models.PropertyChanged += Models_PropertyChanged; },
                () => { models.PropertyChanged -= Models_PropertyChanged; }
            );

一個目の

models.PropertyChanged += Models_PropertyChanged;

は、ListenerManager.Register() を見れば分かるように、即座に実施されます。

ListenerManager を Dispose します。

そのインスタンスの解放時に*3

                    if (listeners != null) {
                        listeners.Dispose();
                        listeners = null;
                    }

と Dispose() を呼べば全て解決。してるのかな?とりあえず動いてるけど。

では!

*1:MSも奨めてないし

*2:既出なのかは調べていません。^^;

*3:IDisposableにして解放するのがよいかと