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

XORveR.com の日記

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

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

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) {
        }

    }

XORveR.com からの挨拶

はじめまして。
XORveR.com 代表の加沢です。

XORveR.com の目的

独自開発のXORveR技術の事業化を目的としています。
XORveR.com 公式ホームページ

XORveR技術とは

既知平文攻撃 (wikipwdia)に対して、利用可能な平文-暗号文ペアの数に上限を設けるという技術です。

ちなみに特許化されています。
特許情報プラットフォーム(J-PlatPat)の「特許・実用新案番号照会」から「特許広報・公告特許公報(B)」に 5992651 を入力して検索することで特許5992651として公開されています。

具体的な効果は

高階差分攻撃法という攻撃手法があります。
これはKN-Cipher (en:wikipwdia)という暗号を32個の選択平文で解読が可能といわれます。
しかし、これに対してXORveR技術でガードすることで、25個以上の選択平文を使うこの攻撃は無効となります。

このように既に過去の暗号方式でも、暗号化のスピードに優れるなどのメリットがあるならば、再利用する可能性を生み出します。

例えばRC4も、128ビット鍵ですから16個以上の選択平文を使う攻撃は無駄です。
RC4の速度と小ささはRF-IDなど非接触ICチップの通信には最適です。

XORveR.comは、IoTの要となるキラーテクノロジとしてこの技術の採用を目指しています。

では!

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

他の記事との関連

心療医の処方薬でふらふらしてやった。風呂はいって寝る。」に機能追加バージョンがあります。
機能追加バージョンでは 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にして解放するのがよいかと