ようこそ。睡眠不足なプログラマのチラ裏です。

SilverlightやWPFやってっと、弱い参照のキーと値を管理するWeakDictionaryが欲しくなるよね。

「弱い参照」と「強い参照」

.NET Frameworkでは、ガベージコレクションによってメモリが管理されます。
変数(フィールドやローカル変数など)に格納されている参照のことを「強い参照」と言います。
強い参照によるオブジェクトは、そのオブジェクトがアクセス可能である限り、ガベージコレクションによって回収されることはありません。
オブジェクトがアクセス不可能になるとオブジェクトのメモリはガベージコレクションの対象となり、適切なタイミング回収されます。


「弱い参照」とは、 WeakReference クラスによる参照です。WeakReference クラスは、コンストラクタで対象オブジェクトを受け取ります。
対象オブジェクトへはTargetプロパティを通してアクセスすることができます。WeakReference オブジェクトは、内部に強い参照を保持せず、
参照をIntPtr構造体として保持しています。これにより、対象オブジェクトはアクセス可能でありながらも、ガベージコレクションの対象となります。
対象オブジェクトがガベージコレクションの対象となるので、対象オブジェクトのメモリが回収された場合、
WeakReferenceオブジェクトのTargetプロパティはnull参照を返します。対象オブジェクトが弱い参照である限り、
ガベージコレクションによって回収されるタイミングは神のみぞ知る状態なので、対象オブジェクトにアクセスする際は、
必ずTargetプロパティから強い参照を取得し、その強い参照を通してアクセスします。


MSDN - 弱い参照
http://msdn.microsoft.com/ja-jp/library/ms404247.aspx



「弱い参照」のキーと値を管理できるDictionaryクラスが欲しい

WPFSilverlightで開発するような場合、メモリリーク問題を避けるために、List や Dictionary
あるいは、Dictionary なんて型を宣言している開発者は少なくないでしょう。
確かにこれらの型を用いることで、メモリリーク問題を解決することが可能です。
しかし、この手法では開発者が「強い参照」と「弱い参照」について、どうしても意識する必要がでてきます。
これは、コードのシンプルさを欠くことを意味します。コードが複雑化すると、無用なバグを招く可能性が高くなってしまいます。
このような理由から、「弱い参照」のキーと値を容易に管理できるDictionaryクラスである
WeakDictionaryクラスが欲しくなるというのは当然の流れです。


Javaであれば、弱い参照の値を管理できる WeakHashMap なんてのがありますが、.NETでは標準で用意されていません。
例えば、Infragistics社のNetAdvantageを利用している方であれば、そちらで提供されているWeakDictionaryを利用できますが、
そうでないのであれば、これを自作する必要がでてきます。既に自作して利用している人も多いとは思いますが、
今回は、その「弱い参照」のキーと値を管理できるWeakDictionaryクラスの実装サンプルをご覧いただきます。


Infragistics社のNetAdvantage - WeakDictionary
http://help.jp.infragistics.com/Help/NetAdvantage/WPF/2009.1/CLR3.0/html/Infragistics3.Wpf.v9.1~Infragistics.Windows.Helpers.WeakDictionary%602.html


「強い参照」と「弱い参照」を同一視する

キーまたは値について、「強い参照」または「弱い参照」を任意に指定することが可能なWeakDictionaryを作ることを考える場合、
強い参照と弱い参照を同じように扱える必要があります。C#4.0であればdynamicを使うことで動的に作ることも可能ですが、
この場合は、同じインターフェイスを実装することで同一視するのが現実的でしょう。ここでは参照を表すインターフェイスを作ります。


IReference{T}.cs

namespace ClassLibrary1
{
    /// <summary>
    /// IReference{T}
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IReference<T>
    {
        T Target { get; }
        bool IsAlive { get; }
    }
}

弱い参照を表す WeakReferenceクラスを作成し、 IReference を実装します。
GetHashCodeは、WeakDictionaryでキーを比較する際に利用します。
ここでは、弱い参照の対象オブジェクトのハッシュコードを返すようにします。


WeakReference{T}.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ClassLibrary1
{
    /// <summary>
    /// WeakReference{T}
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class WeakReference<T>
        : WeakReference, IReference<T>
    {
        private readonly int? _hashCode = null;
        protected WeakReference(T target)
            : base(target, false)
        {
            _hashCode = this.GetHashCode();
        }

        public static WeakReference<T> Create(T target)
        {
            if (target == null)
                return WeakNullReference<T>.Singleton;

            return new WeakReference<T>(target);
        }

        public override int GetHashCode()
        {
            if (_hashCode != null) return _hashCode.Value;
            var value = base.Target;
            if (value == null) return WeakNullReference<T>.Singleton.GetHashCode();
            return value.GetHashCode();
        }

        #region IReference<T> メンバ

        public new T Target
        {
            get { return (T)base.Target; }
        }
        #endregion

        public static implicit operator T(WeakReference<T> reference)
        {
            if (reference != null)
            {
                return reference.Target;
            }
            return default(T);
        }
    }

    public static class Singleton<T> where T : class, new()
    {
        private static T _t = new T();
        public static T Instance { get { return _t; } }
    }
}

対象オブジェクトがnullの場合の表現。シングルトンで扱うのがいいでしょう。

WeakNullReference{T}.cs

namespace ClassLibrary1
{
    /// <summary>
    /// WeakNullReference{T}
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class WeakNullReference<T> : WeakReference<T>
    {
        private WeakNullReference() : base(default(T)) { }

        public static readonly WeakNullReference<T> _singleton = new WeakNullReference<T>();
        public static WeakNullReference<T> Singleton
        {
            get
            {
                return _singleton;
            }
        }

        public override bool IsAlive
        {
            get
            {
                return true;
            }
        }
    }
}

同じく、強い参照を表すStrongReferenceクラスを作ります。
こちらは、同一視するためだけの対象オブジェクトのただのラップです。


StrongReference{T}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClassLibrary1
{
    /// <summary>
    /// StrongReference{T}
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class StrongReference<T>
        : IReference<T>
    {
        private readonly T _target = default(T);
        protected StrongReference(T target)
        {
            _target = target;
        }

        public static StrongReference<T> Create(T target)
        {
            return new StrongReference<T>(target);
        }

        public override int GetHashCode()
        {
            return _target.GetHashCode();
        }

        #region IReference<T> メンバ

        public T Target
        {
            get { return this._target; ; }
        }

        public bool IsAlive
        {
            get { return true; }
        }
        #endregion

        public static implicit operator T(StrongReference<T> reference)
        {
            if (reference != null)
            {
                return reference.Target;
            }
            return default(T);
        }
    }
}

WeakDictionaryの実装

今回のメインであるWeakDictionaryクラスです。


コンストラクタでcomparerを省略した際、neueccさんによる、AnonymousComparer - lambda compare selector for Linq(ver.1.2.0.0)を利用させていただいています。
これを利用することで、Dictionary内のキー比較表現であるIEqualityComparerインターフェイス実装をラムダ式で容易にクリエイトすることができます。
これはガチで便利なライブラリなので、まだの方はぜひダウンロードすると幸せになれると思います。
その他細かい実装については、コードをご覧下さい。


WeakDictionary{TKey, TValue}.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace ClassLibrary1
{
    /// <summary>
    /// WeakDictionary{TKey, TValue}
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    public sealed class WeakDictionary<TKey, TValue>
        : AbstractDictionary<TKey, TValue>
        where TKey : class
        where TValue : class
    {
        #region フィールド
        private readonly Dictionary<IReference<TKey>, IReference<TValue>> _dictionary = null;
        private readonly bool _manageKeysAsWeakReferences = false;
        private readonly bool _manageValuesAsWeakReferences = false;
        #endregion

        #region コンストラクタ

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="manageKeysAsWeakReferences">キーを弱い参照として管理するか否か</param>
        /// <param name="manageValuesAsWeakReferences">値を弱い参照として管理するか否か</param>
        public WeakDictionary(bool manageKeysAsWeakReferences
                            , bool manageValuesAsWeakReferences)
            : this(manageKeysAsWeakReferences, manageValuesAsWeakReferences, 0, null) { }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="manageKeysAsWeakReferences">キーを弱い参照として管理するか否か</param>
        /// <param name="manageValuesAsWeakReferences">値を弱い参照として管理するか否か</param>
        /// <param name="capacity">キャパ</param>
        public WeakDictionary(bool manageKeysAsWeakReferences
                            , bool manageValuesAsWeakReferences
                            , int capacity)
            : this(manageKeysAsWeakReferences, manageValuesAsWeakReferences, capacity, null) { }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="manageKeysAsWeakReferences">キーを弱い参照として管理するか否か</param>
        /// <param name="manageValuesAsWeakReferences">値を弱い参照として管理するか否か</param>
        /// <param name="comparer">キーが等しいかどうかの比較</param>
        public WeakDictionary(bool manageKeysAsWeakReferences
                            , bool manageValuesAsWeakReferences
                            , IEqualityComparer<IReference<TKey>> comparer)
            : this(manageKeysAsWeakReferences, manageValuesAsWeakReferences, 0, comparer) { }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="manageKeysAsWeakReferences">キーを弱い参照として管理するか否か</param>
        /// <param name="manageValuesAsWeakReferences">値を弱い参照として管理するか否か</param>
        /// <param name="capacity">キャパ</param>
        /// <param name="comparer">キーが等しいかどうかの比較</param>
        public WeakDictionary(bool manageKeysAsWeakReferences
                            , bool manageValuesAsWeakReferences
                            , int capacity
                            , IEqualityComparer<IReference<TKey>> comparer)
        {
            this._manageKeysAsWeakReferences = manageKeysAsWeakReferences;
            this._manageValuesAsWeakReferences = manageValuesAsWeakReferences;
            if (comparer == null)
                comparer = AnonymousComparer.Create<IReference<TKey>>
                    ((x, y) => x.GetHashCode() == y.GetHashCode(), x => x.GetHashCode());

            comparer = new ReferenceComparer<TKey>(comparer);
            this._dictionary = new Dictionary<IReference<TKey>, IReference<TValue>>(capacity, comparer);
        }
        #endregion

        #region プロパティ
        /// <summary>
        /// Countメソッドのプロセス中にガベージが集められる可能性があるので、
        /// このプロパティが返した後の項目数が変更される可能性があります。
        /// </summary>
        public override int Count
        {
            get
            {
                RemoveDeadKeys();
                return this._dictionary.Count;
            }
        }
        #endregion

        #region メソッド
        /// <summary>
        /// 指定したキーと値をディクショナリに追加します。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public override void Add(TKey key, TValue value)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            var referenceKey = CreateKeyReference(key);
            var referenceValue = CreateValueReference(value);
            this._dictionary.Add(referenceKey, referenceValue);
        }

        /// <summary>
        /// 指定したキーがディクショナリに格納されている。且つ、
        /// 有効な参照を持っている場合にtrueを返します。
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public override bool ContainsKey(TKey key)
        {
            try
            {
                var referenceKey = CreateKeyReference(key);
                if (!this._dictionary.ContainsKey(referenceKey)) return false;

                IReference<TValue> weakValue;
                bool containts = this._dictionary.TryGetValue(referenceKey, out weakValue);
                if (!containts) return false;
                if (weakValue.Target == null) return false;
                return true;

            }
            finally
            {
                RemoveDeadKeys();
            }
        }

        /// <summary>
        /// 指定したキーを持つ値をディクショナリから削除します。
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public override bool Remove(TKey key)
        {
            return this._dictionary.Remove(CreateKeyReference(key));
        }

        /// <summary>
        /// 指定したキーに関連付けられている値の取得を試みます。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TryGetValue(TKey key, out TValue value)
        {
            IReference<TValue> weakValue;
            if (this._dictionary.TryGetValue(CreateKeyReference(key), out weakValue))
            {
                value = weakValue.Target;
                return weakValue.IsAlive;
            }
            value = null;
            return false;
        }

        /// <summary>
        /// 指定したキーに値を関連付けます。
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected override void SetValue(TKey key, TValue value)
        {
            var weakKey = CreateKeyReference(key);
            this._dictionary[weakKey] = CreateValueReference(value);
        }

        /// <summary>
        /// ディクショナリからすべてのキーと値を削除します。
        /// </summary>
        public override void Clear()
        {
            this._dictionary.Clear();
        }

        /// <summary>
        /// ディクショナリを反復処理する列挙子を返します。
        /// </summary>
        /// <returns></returns>
        public override IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (var kvp in this._dictionary)
            {
                var weakKey = (IReference<TKey>)(kvp.Key);
                var weakValue = kvp.Value;
                var key = weakKey.Target;
                var value = weakValue.Target;

                if (key != null && value != null)
                    yield return new KeyValuePair<TKey, TValue>(key, value);
            }
        }

        /// <summary>
        /// ガベージコレクションを実行後、キーが無効なディクショナリの項目を削除します。 
        /// ディクショナリ項目は、Compactメソッドのプロセス中にガベージが集められる可能性があるので、
        /// このメソッドが返した後にすべての項目が有効であることは保証されません。
        /// </summary>
        /// <param name="removeEntriesWithNullValues"></param>
        public void Compact()
        {
            GC.Collect();
            RemoveDeadKeys();
        }

        /// <summary>
        /// キーが無効なディクショナリの項目を削除します。
        /// </summary>
        private void RemoveDeadKeys()
        {
            List<IReference<TKey>> toRemove = null;
            foreach (var pair in this._dictionary)
            {
                var weakKey = (IReference<TKey>)(pair.Key);
                var weakValue = (IReference<TValue>)pair.Value;

                if (weakKey.IsAlive && weakValue.IsAlive) continue;
                if (toRemove == null)
                    toRemove = new List<IReference<TKey>>();
                toRemove.Add(weakKey);
            }

            if (toRemove == null) return;

            foreach (var key in toRemove)
                this._dictionary.Remove(key);
        }

        /// <summary>
        /// キー参照オブジェクトを生成します。
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private IReference<TKey> CreateKeyReference(TKey key)
        {
            if (this._manageKeysAsWeakReferences)
                return WeakReference<TKey>.Create(key); ;
            return StrongReference<TKey>.Create(key);
        }

        /// <summary>
        /// 値参照オブジェクトを生成します。
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private IReference<TValue> CreateValueReference(TValue value)
        {
            if (this._manageValuesAsWeakReferences)
                return WeakReference<TValue>.Create(value); ;
            return StrongReference<TValue>.Create(value);
        }
        #endregion
    }
}


WeakDictionaryでのキー比較

WeakDictionaryで有効なキーと無効なキーを判断するための
IEqualityComparerインターフェイスを実装したクラスを用意します。
コンストラクタで指定されたcomparerは、Equalsメソッドの最後に判定されます。


ReferenceComparer{T}.cs

using System.Collections.Generic;

namespace ClassLibrary1
{
    /// <summary>
    /// ReferenceComparer{T}
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class ReferenceComparer<T>
        : IEqualityComparer<IReference<T>>
    {
        private readonly IEqualityComparer<IReference<T>> _comparer = null;

        internal ReferenceComparer(IEqualityComparer<IReference<T>> comparer)
        {
            if (comparer == null)
                comparer = EqualityComparer<IReference<T>>.Default;

            this._comparer = comparer;
        }

        public bool Equals(IReference<T> x, IReference<T> y)
        {
            bool xIsDead, yIsDead;
            var left = GetTarget(x, out xIsDead);
            var right = GetTarget(y, out yIsDead);

            if (xIsDead) return yIsDead ? left == right : false;
            if (yIsDead) return false;

            return this._comparer.Equals(left, right);
        }

        private static IReference<T> GetTarget(IReference<T> reference, out bool isDead)
        {
            isDead = !reference.IsAlive;
            return reference;
        }

        public int GetHashCode(IReference<T> obj)
        {
            if (obj != null) return obj.GetHashCode();
            return this._comparer.GetHashCode(obj);
        }
    }
}

Dictionaryの基本実装です。

AbstractDictionary{TKey,TValue}.cs

using System;
using System.Collections.Generic;

namespace ClassLibrary1
{
    /// <summary>
    /// AbstractDictionary{TKey,TValue}
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    public abstract class AbstractDictionary<TKey, TValue>
        : IDictionary<TKey, TValue>
    {
        private KeyCollection _keys;
        private ValueCollection _values;

        protected AbstractDictionary() { }

        public abstract int Count { get; }
        public abstract void Add(TKey key, TValue value);
        public abstract bool Remove(TKey key);
        public abstract bool ContainsKey(TKey key);
        public abstract bool TryGetValue(TKey key, out TValue value);
        public abstract IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
        public abstract void Clear();
        protected abstract void SetValue(TKey key, TValue value);

        public ICollection<TKey> Keys
        {
            get
            {
                if (this._keys == null)
                    this._keys = new KeyCollection(this);

                return this._keys;
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                if (this._values == null)
                    this._values = new ValueCollection(this);

                return this._values;
            }
        }

        public TValue this[TKey key]
        {
            get
            {
                TValue value;
                if (!this.TryGetValue(key, out value))
                    throw new KeyNotFoundException();

                return value;
            }
            set
            {
                SetValue(key, value);
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            this.Add(item.Key, item.Value);
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            TValue value;
            if (!this.TryGetValue(item.Key, out value))
                return false;

            return EqualityComparer<TValue>.Default.Equals(value, item.Value);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            Copy(this, array, arrayIndex);
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            if (!this.Contains(item))
                return false;

            return this.Remove(item.Key);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private static void Copy<T>(ICollection<T> source, T[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");

            if (arrayIndex < 0 || arrayIndex > array.Length)
                throw new ArgumentOutOfRangeException("arrayIndex");

            if ((array.Length - arrayIndex) < source.Count)
                throw new ArgumentException("source, arrayIndex");

            foreach (T item in source)
                array[arrayIndex++] = item;
        }

        private abstract class Collection<T>
            : ICollection<T>
        {
            protected readonly IDictionary<TKey, TValue> _dictionary;

            public Collection(IDictionary<TKey, TValue> dictionary)
            {
                _dictionary = dictionary;
            }

            public int Count
            {
                get { return this._dictionary.Count; }
            }

            public bool IsReadOnly
            {
                get { return true; }
            }

            public void CopyTo(T[] array, int arrayIndex)
            {
                Copy(this, array, arrayIndex);
            }

            public virtual bool Contains(T item)
            {
                foreach (T element in this)
                    if (EqualityComparer<T>.Default.Equals(element, item))
                        return true;
                return false;
            }

            public IEnumerator<T> GetEnumerator()
            {
                foreach (var pair in this._dictionary)
                    yield return GetItem(pair);
            }

            protected abstract T GetItem(KeyValuePair<TKey, TValue> pair);

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #region NotSupported
            public bool Remove(T item)
            {
                throw new NotSupportedException();
            }

            public void Add(T item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }
            #endregion
        }

        private class KeyCollection : Collection<TKey>
        {
            public KeyCollection(IDictionary<TKey, TValue> dictionary)
                : base(dictionary) { }

            protected override TKey GetItem(KeyValuePair<TKey, TValue> pair)
            {
                return pair.Key;
            }

            public override bool Contains(TKey item)
            {
                return this._dictionary.ContainsKey(item);
            }
        }

        private class ValueCollection : Collection<TValue>
        {
            public ValueCollection(IDictionary<TKey, TValue> dictionary)
                : base(dictionary) { }

            protected override TValue GetItem(KeyValuePair<TKey, TValue> pair)
            {
                return pair.Value;
            }
        }
    }
}

単体テスト

かなり端折りましたが、一応テスト駆動で作りました。
VS2010のテストで。

using System;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ClassLibrary1;

namespace TestProject1
{
    /// <summary>
    /// UnitTest1 の概要の説明
    /// </summary>
    [TestClass]
    public class UnitTest1
    {
        public UnitTest1()
        {
            //
            // TODO: コンストラクター ロジックをここに追加します
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///現在のテストの実行についての情報および機能を
        ///提供するテスト コンテキストを取得または設定します。
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region basic
        [TestMethod]
        public void TestStrongKeyStrongValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, false);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            value = null;
            key = null;

            Assert.IsTrue(wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value.Num == 1);
        }

        [TestMethod]
        public void TestStrongKeyStrongValueGC()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, false);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            value = null;
            key = null;
            GC.Collect();

            Assert.IsTrue(wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value.Num == 1);
        }

        [TestMethod]
        public void TestStrongKeyWeakValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, true);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            value = null;

            Assert.IsTrue(wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value.Num == 1);
        }

        [TestMethod]
        public void TestStrongKeyWeakValueGC()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, true);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            value = null;
            GC.Collect();

            Assert.IsTrue(!wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(!wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value == null);
        }

        [TestMethod]
        public void TestWeakKeyStrongValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(true, false);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            key = null;

            Assert.IsTrue(wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value.Num == 1);
        }

        [TestMethod]
        public void TestWeakKeyStrongValueGC()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(true, false);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            key = null;
            GC.Collect();

            Assert.IsTrue(!wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(!wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value == null);
        }

        [TestMethod]
        public void TestWeakKeyWeakValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(true, true);

            var key = new TestKey("aaa");
            var value = new TestValue(1);

            wdic.Add(key, value);

            key = null;
            value = null;

            Assert.IsTrue(wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value.Num == 1);
        }

        [TestMethod]
        public void TestWeakKeyWeakValueGC()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(true, true);

            var key = new TestKey("aaa");
            var value = new TestValue();

            wdic.Add(key, value);

            key = null;
            value = null;
            GC.Collect();

            Assert.IsTrue(!wdic.ContainsKey(new TestKey("aaa")));
            Assert.IsTrue(!wdic.TryGetValue(new TestKey("aaa"), out value));
            Assert.IsTrue(value == null);
        }
        #endregion

        #region for each
        [TestMethod]
        public void TestForEachWeakKeyStrongValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(true, false);
            var a = new TestKey("a");
            var b = new TestKey("b");
            var c = new TestKey("c");
            var d = new TestKey("d");

            var zero = new TestValue(0);
            var one = new TestValue(1);
            var two = new TestValue(2);
            var three = new TestValue(3);

            wdic.Add(a, zero);
            wdic.Add(b, one);
            wdic.Add(c, two);
            wdic.Add(d, three);

            a = null;
            c = null;
            GC.Collect();

            foreach (var item in wdic)
                Debug.Print(item.Value.ToString());

            Assert.IsTrue(wdic.Count == 2);
        }

        [TestMethod]
        public void TestForEachStrongKeyWeakValue()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, true);
            var a = new TestKey("a");
            var b = new TestKey("b");
            var c = new TestKey("c");
            var d = new TestKey("d");

            var zero = new TestValue(0);
            var one = new TestValue(1);
            var two = new TestValue(2);
            var three = new TestValue(3);

            wdic.Add(a, zero);
            wdic.Add(b, one);
            wdic.Add(c, two);
            wdic.Add(d, three);

            zero = null;
            three = null;
            GC.Collect();

            foreach (var item in wdic)
                Debug.Print(item.Value.ToString());

            Assert.IsTrue(wdic.Count == 2);
        }

        [TestMethod]
        public void Clear()
        {
            var wdic = new WeakDictionary<TestKey, TestValue>(false, true);
            var a = new TestKey("a");
            var b = new TestKey("b");
            var c = new TestKey("c");
            var d = new TestKey("d");

            var zero = new TestValue(0);
            var one = new TestValue(1);
            var two = new TestValue(2);
            var three = new TestValue(3);

            wdic.Add(a, zero);
            wdic.Add(b, one);
            wdic.Add(c, two);
            wdic.Add(d, three);


            wdic.Clear();
            Assert.IsTrue(wdic.Count == 0);
        }
        #endregion
    }

    class TestKey : ValueObject<TestKey>
    {
        private readonly string _target = null;
        public TestKey(string target)
        {
            _target = target;
        }

        public override string ToString()
        {
            return _target.ToString();
        }
    }

    class TestValue
    {
        int? _num = null;
        public TestValue() { }
        public TestValue(int? num)
        {
            _num = num;
        }

        public int? Num
        {
            get { return _num; }
        }

        public override string ToString()
        {
            return this._num.ToString();
        }
    }
}

ValueObjectの抽象表現です。
Immutableな値オブジェクトを表現する際に便利です。テスト内で使っています。


ValueObject{TSelf}.cs

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ClassLibrary1
{
    /// <summary>
    /// ValueObject(値オブジェクト)
    /// Immutableオブジェクトの抽象表現
    /// </summary>
    /// <typeparam name="TSelf"></typeparam>
    [Serializable]
    [ComVisible(true)]
    public abstract class ValueObject<TSelf> : IEquatable<TSelf>
        where TSelf : ValueObject<TSelf>
    {
        protected ValueObject()
        {
            foreach (var field in GetFields())
            {
            }
        }

        private void ObjectInvariant()
        {
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            var other = obj as TSelf;
            if (object.ReferenceEquals(other, null)) return false;
            return Equals(other);
        }

        public override int GetHashCode()
        {
            int hashCode = 11;
            int multiplier = 29;

            var fields = GetFields();

            foreach (var field in fields.Select((x, i) => new { Value = x, Index = i }))
            {
                var value = field.Value.GetValue(this);
                if (value == null) continue;
                hashCode *= multiplier;
                hashCode += (value.GetHashCode() + field.Index);
            }
            return hashCode;
        }

        public virtual bool Equals(TSelf other)
        {
            if (other == null) return false;

            Type t = GetType();
            Type otherType = other.GetType();
            if (t != otherType) return false;

            var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            foreach (var field in fields)
            {
                var x = field.GetValue(other);
                var y = field.GetValue(this);

                if (x == null && y != null) return false;
                if (!x.Equals(y)) return false;
            }
            return true;
        }

        protected IEnumerable<FieldInfo> GetFields()
        {
            Type t = GetType();
            var fields = new List<FieldInfo>();

            while (t != typeof(object))
            {
                fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
                t = t.BaseType;
            }
            return fields;
        }

        public static bool operator ==(ValueObject<TSelf> x, ValueObject<TSelf> y)
        {
            if (object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return true;
            if (object.ReferenceEquals(x, null)) return false;
            return x.Equals(y);
        }

        public static bool operator !=(ValueObject<TSelf> x, ValueObject<TSelf> y)
        {
            return !(x == y);
        }
    }
}


以上が弱い参照のキーまたは値を管理できるWeakDictionaryの実装例になります。
気にくわない部分があれば、ご自由に修正してご利用ください。何かの参考になれば幸いです。