2010-05-06 2 views
25

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

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

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

+0

http://blogs.msdn.com/b/nicholg/archive/2006/06/04/617466.aspx – Mark

+3

Хотя это не 'IDictionary', [ConditionalWeakTable] (http://msdn.microsoft. com/en-us/library/dd287757.aspx) - это то, что я действительно искал, когда Google привел меня сюда. Благодаря [этому ответу] (http://stackoverflow.com/questions/5764556/best-time-to-cull-weakreferences-in-a-collection-in-net/5764855#5764855). –

+0

Удивительное открытие! –

ответ

24

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

+2

Следует отметить, что этот класс использует 'ReferenceEquals', а не' GetHashCode' и 'Equals' для выполнения проверок равенства. См. Http://stackoverflow.com/a/8441180/167251 для более подробного обсуждения. – larsmoa

+16

ОП просит слабый словарь со слабыми ** значениями **. Класс .NET 'ConditionalWeakTable' - слабый словарь со слабыми ** ключами **. Поэтому я не верю, что это правильный ответ. –

+2

Но это то, что я точно ищу: P так спасибо за подтверждение этого! – schwarz

6

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

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

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); 
    } 
} 
+8

Будьте осторожны, что сбор мусора может происходить между 'reference.IsAlive' и 'reference.Target', тем самым делая это решение склонным к условиям гонки. –

+0

Достаточно легко поставить Target на свой собственный стек, прежде чем проверять IsAlive, который исправляет эту гонку. –

+1

Почему вы не используете WeakReference.TryGetValue? – TamusJRoyce

0

Это одна вещь, чтобы иметь WeakReferences до значений, но я обнаружил, что ключи в словарях также может быть источником утечки памяти. Вот голая реализация кости с 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 

} 
+0

Относится ли он с Java? –

+0

Это не переносится с любого Java-кода –

0

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

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

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

+0

Существует способ получить уведомление после сбора объекта. ConditionalWeakTable может помочь здесь. Подробности см. В моем блоге [WeakTable] (http://www.nesterovsky-bros.com/weblog/2014/01/08/WeakTable.aspx). –

0

Если сравнение идентичности невозможно использовать, то ConditionalWeakTable не является вариантом.

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