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

XORveR.com の日記

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

心療医の処方薬でふらふらしてやった。風呂はいって寝る。

DotNet wpf

2016/09/19 訂正

            registered.Remove(key);

を入れ忘れていました。

記事の要旨

ListenerManager への対応を進めていて問題が出ました。
入れ替わりの激しいインスタンスの場合、管理クラスでもイベント購読の設定/解除がインスタンス単位でできないと困ります。

インスタンス単位で解放

できるように改良しました。
ListenerManager.cs を差し替えてご使用ください。

ちなみにこのブログに載せるコードはパブリックドメインです。ご自由に改変・配布してください。無保証ですが。

using System;
using System.Collections.Generic;

namespace XORveR.Util {
    /// <summary>
    /// イベントリスナーの解放漏れの対策。
    /// </summary>
    public class ListenerManager : IDisposable {
        private static readonly object nan = new object();
        private Dictionary<object, List<Action>> registered = new Dictionary<object, List<Action>>();
        /// <summary>
        /// イベント購読の登録と解除を設定します。
        /// </summary>
        /// <param name="addAction">イベント購読を設定するアクション。</param>
        /// <param name="removActione">イベント購読を解除するアクション。</param>
        /// <param name="key">インスタンスの識別オブジェクト</param>
        public void Register(Action addAction, Action removActione, object key = null) {
            if (key == null) {
                key = nan;
            }
            lock (registered) {
                addAction();
                if (registered.ContainsKey(key) == false) {
                    registered.Add(key, new List<Action>());
                }
                registered[key].Add(removActione);
            }
        }
        /// <summary>
        /// 入れ替わりの激しいインスタンスへのイベント設定を、インスタンス単位で解放します。
        /// </summary>
        /// <param name="key">インスタンスの識別オブジェクト</param>
        public void DisposeOne(object key) {
            if (key == null) {
                key = nan;
            }
            if (registered.ContainsKey(key) == false) {
                return;
            }
            foreach (Action removeAction in registered[key]) {
                removeAction();
            }
            registered.Remove(key);
        }

        #region IDisposable Support
        private bool disposedValue = false;

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

                if (disposing) {
                    foreach (object key in registered.Keys) {
                        foreach (Action removeAction in registered[key]) {
                            removeAction();
                        }
                    }
                    registered.Clear();
                    registered = null;
                }

                //disposedValue = true;
            }
        }

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

テストコード抜粋

    public class ListenerManagerTests {
        class Notify : INotifyPropertyChanged {
            public event PropertyChangedEventHandler PropertyChanged;
            public event EventHandler Event;
            public int addedCount() {
                return addedCount1() + addedCount2();
            }
            public int addedCount1() {
                var dels = PropertyChanged?.GetInvocationList();
                return dels == null ? 0 : dels.Length;
            }
            public int addedCount2() {
                var dels = Event?.GetInvocationList();
                return dels == null ? 0 : dels.Length;
            }
        }

        [TestMethod()]
        public void DisposeOneTest() {
            var not1 = new Notify();
            var not2 = new Notify();
            var listeners = new ListenerManager();
            listeners.Register(
                () => { not1.PropertyChanged += _PropertyChanged; },
                () => { not1.PropertyChanged -= _PropertyChanged; },
                not1
                );
            listeners.Register(
                () => { not1.Event += _Event; },
                () => { not1.Event -= _Event; },
                not1
                );
            listeners.Register(
                () => { not2.PropertyChanged += _PropertyChanged; },
                () => { not2.PropertyChanged -= _PropertyChanged; },
                not2
                );
            listeners.Register(
                () => { not2.Event += _Event; },
                () => { not2.Event -= _Event; },
                not2
                );
            Assert.AreEqual(2, not1.addedCount());
            Assert.AreEqual(2, not2.addedCount());
            listeners.DisposeOne(not2);
            Assert.AreEqual(2, not1.addedCount());
            Assert.AreEqual(0, not2.addedCount());
            listeners.DisposeOne(not2);
            Assert.AreEqual(2, not1.addedCount());
            Assert.AreEqual(0, not2.addedCount());
            listeners.DisposeOne(null);
            Assert.AreEqual(2, not1.addedCount());
            Assert.AreEqual(0, not2.addedCount());
            listeners.Dispose();
            Assert.AreEqual(0, not1.addedCount());
            Assert.AreEqual(0, not2.addedCount());
        }

        private void _PropertyChanged(object sender, PropertyChangedEventArgs e) {
        }

        private void _Event(object sender, EventArgs e) {
        }

    }