複数のキーを利用できるConditionalWeakTableクラス

下記記事にて、2つのキーを利用できるWeakTableを実装してみました。
2つのキーを利用できるConditionalWeakTableクラス - 何でもプログラミング

今回は3,4個と拡張しやすいように、複数個のキーを利用できるWeakTableを実装してみたいと思います。

Keysクラス

前回は2つのWeakReferenceプロパティをもっていましたが、今回はWeakReference配列を扱うよう変更しました。

EqualsとGetHashCodeを、WeakReferenceではなくTargetで行うところは変わっていません。

class Keys
{
    public WeakReference[] WeakKeys { get; }
    int _hash;

    public bool IsAlive()
    {
        return WeakKeys.All(x => x.IsAlive);
    }

    public Keys(object[] keys)
    {
        WeakKeys = keys.Select(x => new WeakReference(x)).ToArray();

        _hash = keys.Aggregate(365011897, (hash, key) => hash * -1521134295 + key.GetHashCode());
    }

    public override bool Equals(object obj)
    {
        var keys = obj as Keys;
        return keys != null
            && WeakKeys.Length == keys.WeakKeys.Length
            && WeakKeys.Zip(keys.WeakKeys, TargetEquals).All(x => x);
    }

    bool TargetEquals(WeakReference weakRef1, WeakReference weakRef2)
    {
        var ref1 = weakRef1.Target;
        var ref2 = weakRef2.Target;
        return ref1 != null
            && ref2 != null
            && Equals(ref1, ref2);
    }

    public override int GetHashCode()
    {
        return _hash;
    }
}


WeakTableクラス

まず、一般的はkey配列に対応したクラスを作成します。

class WeakTable<TValue>
{
    object _lockObj = new object();
    Dictionary<Keys, TValue> _dictionary = new Dictionary<Keys, TValue>();
    public WeakTable()
    {
        WeakEventManager<GCNotifier, EventArgs>.AddHandler(null, nameof(GCNotifier.Collected), 
            (s, e) => CheckReferences());
    }

    public void Add(object[] keys, TValue value)
    {
        lock (_lockObj)
        {
            _dictionary[new Keys(keys)] = value;
        }
    }

    public bool TryGetValue(object[] keys, ref TValue value)
    {
        lock (_lockObj)
        {
            var key = new Keys(keys);
            if (_dictionary.ContainsKey(key) == false)
                return false;
            value = _dictionary[key];
            return true;
        }
    }

    public void CheckReferences()
    {
        lock (_lockObj)
        {
            _dictionary = _dictionary
                .Where(x => x.Key.IsAlive())
                .ToDictionary(x => x.Key, x => x.Value);
        }
    }
}


これを利用して、例えば2つのキーのWeakTableは下記のように定義します。

class WeakTable<TKey1, TKey2, TValue>
    where TKey1 : class
    where TKey2 : class
{
    WeakTable<TValue> _table = new WeakTable<TValue>();

    public void Add(TKey1 key1, TKey2 key2, TValue value) =>
        _table.Add(new object[] { key1, key2 }, value);

    public bool TryGetValue(TKey1 key1, TKey2 key2, ref TValue value) =>
        _table.TryGetValue(new object[] { key1, key2 }, ref value);
}