弱い参照 WeakReferenceとWeakEventPatternと

基本用法
C#でのメモリ管理は実行環境である.NETお任せです。

使い終わったらガベージコレクタが自動で回収してくれるので、プログラマが逐一破棄処理を明示的に記述する必要はありません。

生成されたインスタンスが使用中か否かは、そのインスタンスへの参照の有無に依ります。これを強い参照という。

一方、弱い参照という機能もあります。参照があってもガベージコレクタにより回収されます。

インスタンスを生成してからそれを使うまでの間に、ガベージコレクタの実行がなければ使えるし、実行されてるとNullが返却されます。

System.WeakReference

公式APIにあるサンプルコードを文法だけ新しくして引用します。
    class Program
    {
        static void Main(string[] args)
        {
            int cacheSize = 50;
            Random r = new Random();
            Cache c = new Cache(cacheSize);

            string DataName = "";
            GC.Collect(0);

            for (int i = 0; i < c.Count; i++)
            {
                int index = r.Next(c.Count);

                DataName = c[index].Name;
            }

            double regenPercent = c.RegenerationCount / (double)c.Count;
            Console.WriteLine($"Cache size: {c.Count}, Regenerated: {regenPercent:P2}%");
        }
    }

    public class Data
    {
        private byte[] _data;

        public string Name { get; }

        public Data(int size)
        {
            _data = new byte[size * 1024];
            Name = size.ToString();
        }
    }

    public class Cache
    {
        static Dictionary<int, WeakReference> _cache;

        public int Count => _cache.Count;

        public int RegenerationCount { get; private set; }

        public Cache(int count)
        {
            _cache = new Dictionary<int, WeakReference>();

            for (int i = 0; i < count; i++)
                _cache.Add(i, new WeakReference(new Data(i), false));
        }

        public Data this[int index]
        {
            get
            {
                Data d = _cache[index].Target as Data;

                if(d == null)
                {
                    Console.WriteLine($"Regenerate object at {index}: Yes");
                    d = new Data(index);
                    _cache[index].Target = d;
                    RegenerationCount++;
                }
                else
                {
                    Console.WriteLine($"Regenerate object at {index}: No");
                }

                return d;
            }
        }
    }
Dataクラスは参照されるターゲットですね。

Cacheクラスでデータクラスのインスタンスを弱い参照でキャッシュしています。

インデクサにより指定インデックスにあるデータを返却しますが、既にGCされている場合には再作成しなおしています。

このコードには2点ほど要点があります。

一つは弱い参照であるターゲットの存在確認が変わったこと。

以前は以下のようなコードでした。
            if(!_cache[index].IsAlive)
            {
                _cache[index].Target = new Data(index);
            }

            return _cache[index].Target as Data;
このIsAliveプロパティは今でも使えるのですが、if文での確認時とreturn文で値を取得するまでの間にGCが実行されないとは限らないので使われなくなっています。

直接取得後にNullか否かで判断していますね。

ジェネリクスであるSystem.WeakRefereceではTryGetTargetというメソッドで取得するようになっています。

もう一つは、キャッシュとして使えないこと。

公式文書にはちょっとだけ記載があります。
メモリ管理の問題への自動的な解決方法として、弱い参照を使用しないでください。 代わりに、アプリケーションのオブジェクトを処理するために、効果的なキャッシュ ポリシーを開発します。
最初のサンプルコードを実行してみると、私のマシン環境では概ね6割程度が再作成となっていました。

GCの実行はVisual Studio若しくはパフォーマンスモニタ等で確認できますが、予想以上に高頻度で実行されています。

とてもじゃないですがキャッシュとして使えるような代物でもありません。

あまり使いどころもなさそうな機能ではあります。

WeakEventパターン

WeakReferenceがほとんど使いどころのない機能であるのに対し、WeakEventパターンは意外とよく目にします。

メモリリークの三大要因にして最大の原因であるのが購読イベントの破棄漏れ対応でです。

寿命の異なるインスタンス間で短い方が長い方のイベントを購読しているのに、先立つ時にその解除をしないとGCの対象とならずにリークとなってしまいます。

例えばWPFのデータバインドではViewがDataContextとなるViewModelのPropertyChangedを購読しますが、WeakEventパターンで実装されています。

実装方法はいろいろあるようですが、以下が参考になりました。
【雑記】弱参照
概要 ガベージ コレクションに関連して、弱参照というものがあります。 めったに使うものではありませんが、使い方・使い道を説明します。 サンプル …
Weak Event Patterns - WPF .NET Framework
Learn about the Windows Presentation Foundation weak event pattern, which addresses the issue of handlers that are not d...
基本用法
スポンサーリンク
C#プログラミング再入門