Хорошая реализация слабого словаря в .Net - PullRequest
27 голосов
/ 07 мая 2010

Где найти хорошую реализацию IDictionary, которая использует слабые ссылки внутри?

Словарь должен содержать только слабые ссылки на значения и в конечном итоге очищать себя от мертвых ссылок.

Или я должен просто написать это сам?

Ответы [ 6 ]

28 голосов
/ 17 октября 2012

Класс ConditionalWeakTable использует слабые ключи и автоматически удаляет запись ключ / значение, как только за пределами таблицы отсутствуют другие ссылки на ключ.

5 голосов
/ 07 мая 2010

Вам нужно написать это самостоятельно. Это должно быть относительно просто, реализуя интерфейс IDictionary и затем сохраняя фактические значения как WeakReferences. Затем вы можете проверить значения при добавлении / выборе, чтобы убедиться, что они все еще живы.

Псевдокод - на самом деле не компилируется:

public class WeakDictionary <TKey,TValue> : IDictionary<TKey,TValue>
{
    private IDictionary<TKey,WeakReference> _innerDictionary = new Dictionary<TKey,WeakReference>();


    public TValue Index[ TKey key ]
    {
        get{
            var reference = _innerDictionary[ key ];
            if( reference.IsAlive )
                return (TValue)reference.Target;
            throw new InvalidOperation( "Key not found." );
        }

    }

    private void Cull()
    {
        var deadKeys = new List<TKey>();
        foreach( var pair in _innerDictionary )
        {
            if( ! pair.Value.IsAlive )
                deadKeys.Add( pair.Key );
        }

        foreach( var key in deadKeys )
            _innerDictionary.Remove( key );
    }
}
0 голосов
/ 19 марта 2019

Это моя версия параллельного слабого (значения) словаря:

public class WeakConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
    private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _internalDictionary =
        new ConcurrentDictionary<TKey, WeakReference<TValue>>();

    public TValue this[TKey key]
    {
        get
        {
            if (_internalDictionary.TryGetValue(key, out var weakReference) &&
                weakReference.TryGetTarget(out var value))
                return value;

            return null;
        }
        set
        {
            _internalDictionary.TryAdd(key, new WeakReference<TValue>(value));
        }
    }

    public ICollection<TKey> Keys => _internalDictionary.Keys;

    public ICollection<TValue> Values => _internalDictionary.Values
        .Select(_ => _.GetTarget())
        .Where(_ => _ != null)
        .ToList();

    public int Count => _internalDictionary.Count;

    public bool IsReadOnly => false;

    public void Add(TKey key, TValue value)
    {
        Purge();
        if (!_internalDictionary.TryAdd(key, new WeakReference<TValue>(value)))
        {
            throw new InvalidOperationException("Key already existing");
        }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException();
    }

    public void Clear()
    {
        _internalDictionary.Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item) => _internalDictionary.TryGetValue(item.Key, out var weakReference) &&
                weakReference.GetTarget() == item.Value;

    public bool ContainsKey(TKey key) => _internalDictionary.TryGetValue(key, out var weakReference) &&
                weakReference.IsAlive();

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        Purge();
        _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value != null)
            .ToList()
            .CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        Purge();
        return _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value != null)
            .GetEnumerator();
    }

    public bool Remove(TKey key)
    {
        return _internalDictionary.TryRemove(key, out var weakReference);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        value = null;
        if (_internalDictionary.TryGetValue(key, out var weakReference))
        {
            value = weakReference.GetTarget();
        }

        return value != null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        Purge();
        return GetEnumerator();
    }

    public void Purge()
    {
        foreach (var itemToRemove in _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value == null))
        {
            _internalDictionary.TryRemove(itemToRemove.Key, out var weakReference);
        }
    }
}

public static class WeakReferenceExtensions
{
    public static bool IsAlive<T>([NotNull] this WeakReference<T> weakReference) where T : class =>
        weakReference.TryGetTarget(out var target);

    public static T GetTarget<T>([NotNull] this WeakReference<T> weakReference, T defaultValue = default(T)) where T : class
    {
        if (!weakReference.TryGetTarget(out T target))
            return defaultValue;

        return target;
    }
}

и тест, доказывающий, что ссылка на значение фактически отбрасывается:

    [TestMethod]
    public void TestWeakDictionary()
    {
        var weakDict = new WeakConcurrentDictionary<string, TestItem>();

        {
            var testItem = new TestItem();
            weakDict.Add("testitem", testItem);

            Assert.AreEqual(1, weakDict.Count);
            Assert.AreSame(testItem, weakDict["testitem"]);
        }

        GC.Collect();
        Assert.IsNull(weakDict["testitem"]);
        weakDict.Purge();
        Assert.AreEqual(0, weakDict.Count);
    }

Некоторые заметки:

  1. Property Keys возвращает все ключи, даже те записи, значение которых было собрано, но Values ​​всегда возвращает живые ненулевые объекты.
  2. этот [ключ] МОЖЕТ вернуть ноль
  3. Вы также можете вызвать Очистить, чтобы очистить записи, значения которых были собраны
  4. Тест работает при компиляции и выполнении в режиме Release
0 голосов
/ 09 января 2014

Если сравнение идентификаторов не может быть использовано, то ConditionalWeakTable не является опцией.

В этом случае я осмелюсь предложить нашу реализацию WeakTable.cs , и наше описание в блоге WeakTable .

0 голосов
/ 26 ноября 2010

Одна проблема, связанная с простым хранением словаря объектов WeakReference, заключается в том, что, кроме перечисления всего словаря, невозможно удалить из словаря любые объекты WeakReference, цели которых выходят за пределы области видимости.

Было бы полезно, если бы WeakReference мог включать делегата, который будет вызываться, когда основная цель вышла из области видимости. Насколько я знаю, это невозможно сделать. Если вы не против добавить другое поле и небольшой код к объектам, которые вы храните в своем «слабом словаре», я бы предложил создать так называемый объект «Finasposer», единственным полем которого является MethodInvoker; при утилизации MethodInvoker должен быть аннулирован; финализатор должен Interlocked.Exchange () для MethodInvoker иметь значение null и - если его старое значение было не нулевым - вызывать его. Объект, который будет записан в словаре, должен создать новый объект Finasposer с делегатом, который будет вызывать удаление ключа из словаря, когда это будет удобно.

Обратите внимание, что ни финализатор, ни любой вызванный им делегат никогда не должны напрямую манипулировать словарем и делать что-либо, что потребовало бы получения блокировки. Если Finasposer содержит делегата, сам этот делегат гарантированно будет действительным при выполнении Finalize, но объект, присоединенный к делегату, и любые объекты, на которые он ссылается, могут находиться в неожиданных состояниях. Однако для безопасного метода, вызываемого Finasposer, должно быть безопасно добавить в связанный список ссылку на объект, который вышел из области видимости. Словарь, Add, Remove и другие методы могут опрашивать связанный список, чтобы определить, не исчезла ли какая-либо из WeakReferences и нет необходимости его удалять.

0 голосов
/ 01 ноября 2010

Слабая ссылка на значения - это одно, но я обнаружил, что ключи словаря также могут быть источником утечек памяти. Вот простая реализация с WeakReference для ключей:

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

namespace Common.library.collections {

    /// <summary>
    /// THIS DICTIONARY WILL NOT "HANG ON" TO THE KEYS IT USES
    /// IF THE KEY IS GARBAGE COLLECTED, THE VALUE WILL BE RELEASED TOO
    /// </summary>
    public class Dictionary_usingWeakKey<K, V> {
        //MAP FROM HASH CODE TO LIST OF KEY/VALUE PAIRS
        private Dictionary<int, List<Pair>> dic = new Dictionary<int, List<Pair>>();


        public void Add(K key, V value) {
            if (value==null){
                this.Remove(key);
                return;
            }//endif

            List<Pair> list = null;
            dic.TryGetValue(key.GetHashCode(), out list);
            if (list == null) {
                list = new List<Pair>();
                dic.Add(key.GetHashCode(), list);
            }//endif

            Boolean isDirty = false;            
            foreach(Pair p in list){
                if (p.Key.Target == null) {
                    isDirty = true;
                    continue;
                }//endif
                if (p.Key.Target == (Object)key) {
                    p.Value = (Object)value;
                    if (isDirty) cleanList(list);
                    return;
                }//endif
            }//for
            if (isDirty) cleanList(list);

            Pair newP=new Pair();
            newP.Key = new WeakReference(key);
            newP.Value = value;
            list.Add(newP);
        }//method


        public bool ContainsKey(K key) {
            List<Pair> list = null;
            dic.TryGetValue(key.GetHashCode(), out list);
            if (list == null) return false;

            Boolean isDirty = false;
            foreach (Pair p in list) {
                if (p.Key.Target == null) {
                    isDirty = true;
                    continue;
                }//endif
                if (p.Key.Target == (Object)key) {
                    if (isDirty) cleanList(list);
                    return true;
                }//endif
            }//for
            if (isDirty) cleanList(list);

            return false;
        }//method



        private void cleanList(List<Pair> list) {
            var temp = (from Pair p in list where p.Key.Target != null select p);
            list.Clear();
            list.AddRange(temp);
        }//method



        public bool Remove(K key) {
            List<Pair> list = null;
            dic.TryGetValue(key.GetHashCode(), out list);
            if (list == null) return true;

            foreach (Pair p in list) {
                if (p.Key.Target == (Object)key) {
                    p.Value = null;
                    break;
                }//endif
            }//for
            cleanList(list);

            return true;
        }//method





        public V this[K key] {
            get {
                List<Pair> list = null;
                dic.TryGetValue(key.GetHashCode(), out list);
                if (list == null) return default(V);

                Boolean isDirty = false;
                foreach (Pair p in list) {
                    if (p.Key.Target == null) {
                        isDirty = true;
                        continue;
                    }//endif

                    if (p.Key.Target == (Object)key) {
                        if (isDirty) cleanList(list);
                        return (V)p.Value;
                    }//endif
                }//for
                if (isDirty) cleanList(list);

                return default(V);
            }
            set {
                this.Add(key, value);
            }
        }


        public void Add(KeyValuePair<K, V> item) {
            throw new NotImplementedException();
        }

        public void Clear() {
            dic.Clear();
        }

        public bool Contains(KeyValuePair<K, V> item) {
            throw new NotImplementedException();
        }

        public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) {
            throw new NotImplementedException();
        }

        public int Count {
            get {
                throw new NotImplementedException();            
                //return dic.Count();           
            }
        }

        public bool IsReadOnly {
            get { return false; }
        }

        public bool Remove(KeyValuePair<K, V> item) {
            throw new NotImplementedException();
        }



        public IEnumerator<KeyValuePair<K, V>> GetEnumerator() {
            throw new NotImplementedException();    
            //return dic.GetEnumerator();
        }


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





    }//class



    public class Pair{
        public WeakReference Key;
        public Object Value;
    }//method

}
...