2010-05-06 14 views
25

¿Dónde puedo encontrar una buena implementación de IDictionary que utiliza referencias débiles en el interior?Buena implementación del diccionario débil en .Net

El diccionario debe contener solo referencias débiles a los valores y eventualmente limpiarse de referencias muertas.

¿O debería simplemente escribirlo yo mismo?

+0

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

+3

Aunque no es un 'IDictionary', el [ConditionalWeakTable] (http://msdn.microsoft. com/en-us/library/dd287757.aspx) es lo que realmente estaba buscando cuando Google me trajo aquí. Gracias a [esta respuesta] (http://stackoverflow.com/questions/5764556/best-time-to-cull-weakreferences-in-a-collection-in-net/5764855#5764855). –

+0

Increíble descubrimiento! –

Respuesta

24

ConditionalWeakTable Class utiliza claves débiles y elimina automáticamente la entrada clave/valor tan pronto como no haya otras referencias a una clave fuera de la tabla.

+2

Cabe señalar que esta clase utiliza 'ReferenceEquals' en lugar de' GetHashCode' y 'Equals' para hacer verificaciones de igualdad. Vea http://stackoverflow.com/a/8441180/167251 para una discusión más a fondo. – larsmoa

+16

El OP está pidiendo un diccionario débil con ** valores ** débiles. La clase .NET 'ConditionalWeakTable' es un diccionario débil con ** claves ** débiles. Por lo tanto, no creo que esta sea una respuesta correcta. –

+2

Pero eso es exactamente lo que estoy buscando: P, ¡gracias por confirmar esto! – schwarz

6

Tendrá que escribirlo usted mismo. Debería ser relativamente sencillo, implementar la interfaz IDictionary y luego almacenar los valores reales como WeakReferences. Luego puede verificar los valores en Agregar/Seleccionar para ver si todavía están vivos.

Pseudo código - en realidad no se compila:

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

Tenga cuidado de que la recolección de basura pueda ocurrir entre 'reference.IsAlive' y 'reference.Target', lo que hace que esta solución sea propensa a las condiciones de carrera. –

+0

Es bastante fácil poner Target en su propia pila antes de comprobar IsAlive que corrige esa carrera –

+1

¿Por qué no usa WeakReference.TryGetValue? – TamusJRoyce

0

Una cosa es tener WeakReferences a los valores, pero he encontrado que las claves de diccionario también pueden ser una fuente de pérdidas de memoria. Aquí es una aplicación huesos desnudo con WeakReference de teclas:

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

¿Está portado desde Java? –

+0

Esto no está portado desde ningún código Java –

0

Un problema con simplemente manteniendo un diccionario de objetos WeakReference es que no hay manera, a falta de enumerar todo el diccionario, de eliminar del diccionario ningún objeto WeakReference cuyos los objetivos quedan fuera del alcance.

Sería útil si una WeakReference pudiera incluir un delegado que se invocaría cuando el objetivo principal quedara fuera del alcance. Hasta donde yo sé, no hay forma de hacer eso. Si no te importa agregar otro campo y un pequeño código a los objetos que estás almacenando en tu "diccionario débil", te sugiero crear lo que llamo un objeto "Finasposer", cuyo único campo es un MethodInvoker; cuando se desecha, el MethodInvoker debe anularse; el finalizador debe Interlocked.Exchange() el MethodInvoker para anular y - si su valor anterior no era nulo - invocarlo. El objeto que se escribirá en el diccionario debe crear un nuevo objeto Finasposer, con un delegado que hará que la clave se elimine del diccionario cuando sea conveniente.

Tenga en cuenta que ni el finalizador ni ningún delegado invocado por ello nunca deben manipular directamente el diccionario, ni hacer nada que requiera la adquisición de un bloqueo. Si Finasposer tiene un delegado, ese delegado se garantiza que será válido cuando se ejecuta Finalize, pero el objeto adjunto al delegado y cualquier objeto al que se haga referencia de este modo puede estar en estados inesperados. Sin embargo, debería ser seguro que el método llamado Finasposer agregue a una lista vinculada una referencia al objeto que salió del alcance. Los métodos Agregar, Eliminar y otros del diccionario podían sondear la lista vinculada para ver si alguna de las WeakReferences allí había muerto y necesitaba ser eliminada.

+0

Hay forma de tener una notificación después de que el objeto se recopila. ConditionalWeakTable puede ayudar aquí. Por favor vea mi blog [WeakTable] (http://www.nesterovsky-bros.com/weblog/2014/01/08/ WeakTable.aspx) para más detalles. –

0

Si no se puede usar la comparación de identidad, ConditionalWeakTable no es una opción.

En este caso, me atrevo a sugerir nuestra aplicación WeakTable.cs, y nuestra descripción en el blog WeakTable.

Cuestiones relacionadas