2010-05-06 17 views
25

Dove posso trovare una buona implementazione di IDictionary che utilizza riferimenti deboli all'interno?Buona implementazione del dizionario debole in .Net

Il dizionario dovrebbe contenere solo riferimenti deboli ai valori e, infine, ripulire se stesso da riferimenti morti.

O dovrei semplicemente scriverlo da solo?

+0

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

+3

Anche se non è un 'IDictionary', la [ConditionalWeakTable] (http://msdn.microsoft. it/it/us/library/dd287757.aspx) è quello che stavo davvero cercando quando Google mi ha portato qui. Grazie a [questa risposta] (http://stackoverflow.com/questions/5764556/best-time-to-cull-weakreferences-in-a-collection-in-net/5764855#5764855). –

+0

Scoperta eccezionale! –

risposta

24

ConditionalWeakTable Class utilizza le chiavi deboli e rimuove automaticamente la chiave/valore immesso non appena nessun altro riferimento a una chiave esiste all'esterno della tabella.

+2

Va notato che questa classe usa 'ReferenceEquals' piuttosto che' GetHashCode' e 'Equals' per fare controlli di uguaglianza. Vedi http://stackoverflow.com/a/8441180/167251 per una discussione più approfondita. – larsmoa

+16

L'OP chiede un dizionario debole con valori ** deboli **. La classe .NET 'ConditionalWeakTable' è un dizionario debole con le ** chiavi ** deboli. Pertanto, non credo che questa sia una risposta corretta. –

+2

Ma questo è quello che sto cercando esattamente: P quindi grazie per aver confermato questo! – schwarz

6

Avrete bisogno di scrivere da soli. Dovrebbe essere relativamente semplice, implementando l'interfaccia IDictionary e quindi memorizzando i valori attuali come WeakReferences. È quindi possibile controllare i valori su Aggiungi/Seleziona per vedere se sono ancora vivi.

Pseudo codice - non sarà davvero compilare:

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

Fai attenzione che la garbage collection possa verificarsi tra 'reference.IsAlive' e 'reference.Target', rendendo così questa soluzione incline alle condizioni di gara. –

+0

È abbastanza facile mettere Target sul proprio stack prima di controllare IsAlive che corregge quella razza –

+1

Perché non stai usando WeakReference.TryGetValue? – TamusJRoyce

0

E 'una cosa per avere WeakReferences ai valori, ma ho scoperto che le chiavi dizionario può anche essere una fonte di perdite di memoria. Ecco un'implementazione nude ossa con WeakReference ai tasti:

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

È stato portato da Java? –

+0

Questo non è portato da alcun codice Java –

0

Un problema con semplicemente in possesso di un dizionario di oggetti WeakReference è che non c'è modo, a corto di enumerare l'intero dizionario, di rimuovere dal Dizionario oggetti WeakReference cui gli obiettivi escono dal campo di applicazione.

Sarebbe utile se un WeakReference potesse includere un delegato che verrebbe richiamato quando l'obiettivo primario è andato fuori dal campo di applicazione. Per quanto ne so, non c'è modo di farlo. Se non ti dispiace aggiungere un altro campo e un piccolo codice agli oggetti che stai memorizzando nel tuo "dizionario debole", ti suggerisco di creare quello che chiamo un oggetto "Finasposer", il cui unico campo è un MethodInvoker; quando è eliminato, MethodInvoker deve essere annullato; il finalizzatore dovrebbe Interlocked.Exchange() il MethodInvoker a null e - se il suo vecchio valore era non nullo - invocarlo. L'oggetto da scrivere nel dizionario dovrebbe creare un nuovo oggetto Finasposer, con un delegato che farà sì che la chiave venga rimossa dal dizionario quando è conveniente.

Si noti che né il finalizzatore né alcun delegato invocato in tal modo non devono mai manipolare direttamente il dizionario, né eseguire operazioni che richiedono l'acquisizione di un blocco. Se il Finasposer detiene un delegato, il delegato stesso è garantito valido quando Finalize viene eseguito, ma l'oggetto collegato al delegato e qualsiasi oggetto a cui si fa riferimento in tal modo potrebbe trovarsi in stati imprevisti. Dovrebbe essere sicuro, tuttavia, per il metodo chiamato da Finasposer per aggiungere a un elenco collegato un riferimento all'oggetto che è andato fuori ambito. Il comando Aggiungi, Rimuovi e altri metodi del dizionario possono eseguire il polling dell'elenco collegato per verificare se una delle WeakReferences in essa contenute sia morta e debba essere eliminata.

+0

C'è un modo per avere una notifica dopo che l'oggetto è stato raccolto. ConditionalWeakTable può aiutare qui. Si prega di consultare il mio blog [WeakTable] (http://www.nesterovsky-bros.com/weblog/2014/01/08/WeakTable.aspx) per i dettagli. –

0

Se non è possibile utilizzare il confronto identità, ConditionalWeakTable non è un'opzione.

In questo caso mi permetto di suggerire la nostra implementazione WeakTable.cs, e la nostra descrizione nel blog WeakTable.

Problemi correlati