2010-05-14 3 views
14

Использование List<WeakReference> не будет работать так, как я хочу. Я хочу, чтобы WeakReferences автоматически удалялся из списка всякий раз, когда объект, с которым они ссылаются, собирает мусор.Есть ли способ сделать WeakList или WeakCollection (например, WeakReference) в среде CLR?

ConditionalWeakTable<TKey,TValue> не удовлетворяет меня, потому что, хотя его ключи и значения слабо указаны и могут быть собраны, вы не можете их перечислить!

+0

Интересно посмотреть два ответа до сих пор и есть не-полностью автоматический шаг продувки. Мне нужно потратить некоторое время на размышления об этом, но это может быть действительно достаточно для того, что мне нужно, даже если оно не полностью автоматическое. –

+1

Очистка наиболее естественно выполняется во время перечисления. Единственный другой вариант - периодическая чистка, и в этом случае решение становится скорее «кешем», чем «слабым списком». 'WeakReference' не предполагается использовать для кеширования; для этого есть лучшие решения (например, [System.Runtime.Caching] (http://msdn.microsoft.com/en-us/library/system.runtime.caching.aspx)). –

+0

Спасибо за интересное предложение о System.Runtime.Caching. Но у меня было особое применение в этом вопросе, и я вижу некоторые несоответствия импеданса - 1) мне не нужны или не нужны строковые ключи для получения элементов, я просто хочу иметь возможность повторять их по требованию.2) Я, вероятно, был бы более счастлив, если бы элементы оставляли кеш из-за сбора мусора, а не по другим разным причинам (например, кеш, используя слишком много памяти). –

ответ

7

Я согласен, что реализация WeakList<T> возможно, но Я не думаю, что это точно легко. Вы можете использовать мою реализацию here. Класс WeakCollection<T> зависит от WeakReference<T>, что в свою очередь зависит от SafeGCHandle.

+0

Это проект кода! Потрясающие. Благодарю. :-) –

+0

@ stephen-cleary - это, кажется, не в вашем последнем источнике, поэтому мне интересно, как вы приближаетесь к этой проблеме с последней CLR. –

+2

@ Mike-EEE: для эфемеронов я использую [Connected Properties] (http://connectedproperties.codeplex.com/). Они не поддерживают перечисление, но мне никогда не нужны эти возможности. –

6

Вы можете легко реализовать класс WeakList<T>, который будет обертывать List<WeakReference>.

Невозможно автоматически удалить объекты, когда они собраны в мусор, поскольку невозможно обнаружить, когда это произойдет. Тем не менее, вы можете удалить «мертвые» (мусорные) объекты, когда вы их встретите, проверив свойство WeakReference.IsAlive. Однако я бы не рекомендовал этот подход, потому что это может привести к запутанному поведению с точки зрения клиента. Вместо этого я рекомендовал бы применить метод Purge для удаления мертвых записей, которые вы бы назвали явно.

Вот пример реализации:

public class WeakList<T> : IList<T> 
{ 
    private List<WeakReference<T>> _innerList = new List<WeakReference<T>>(); 

    #region IList<T> Members 

    public int IndexOf(T item) 
    { 
     return _innerList.Select(wr => wr.Target).IndexOf(item); 
    } 

    public void Insert(int index, T item) 
    { 
     _innerList.Insert(index, new WeakReference<T>(item)); 
    } 

    public void RemoveAt(int index) 
    { 
     _innerList.RemoveAt(index); 
    } 

    public T this[int index] 
    { 
     get 
     { 
      return _innerList[index].Target; 
     } 
     set 
     { 
      _innerList[index] = new WeakReference<T>(value); 
     } 
    } 

    #endregion 

    #region ICollection<T> Members 

    public void Add(T item) 
    { 
     _innerList.Add(new WeakReference<T>(item)); 
    } 

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

    public bool Contains(T item) 
    { 
     return _innerList.Any(wr => object.Equals(wr.Target, item)); 
    } 

    public void CopyTo(T[] array, int arrayIndex) 
    { 
     _innerList.Select(wr => wr.Target).CopyTo(array, arrayIndex); 
    } 

    public int Count 
    { 
     get { return _innerList.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return false; } 
    } 

    public bool Remove(T item) 
    { 
     int index = IndexOf(item); 
     if (index > -1) 
     { 
      RemoveAt(index); 
      return true; 
     } 
     return false; 
    } 

    #endregion 

    #region IEnumerable<T> Members 

    public IEnumerator<T> GetEnumerator() 
    { 
     return _innerList.Select(x => x.Target).GetEnumerator(); 
    } 

    #endregion 

    #region IEnumerable Members 

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

    #endregion 

    public void Purge() 
    { 
     _innerList.RemoveAll(wr => !wr.IsAlive); 
    } 
} 

Этот класс использует следующие классы и методы расширения:

WeakReference<T> (только сильно типизированных обертки вокруг WeakReference)

[Serializable] 
public class WeakReference<T> : WeakReference 
{ 
    public WeakReference(T target) 
     : base(target) 
    { 
    } 

    public WeakReference(T target, bool trackResurrection) 
     : base(target, trackResurrection) 
    { 
    } 

    public WeakReference(SerializationInfo info, StreamingContext context) 
     : base(info, context) 
    { 
    } 

    public new T Target 
    { 
     get 
     { 
      return (T)base.Target; 
     } 
    } 
} 

IndexOf (такой же, как IList<T>.IndexOf, но работает на IEnumerable<T>)

public static int IndexOf<T>(this IEnumerable<T> source, T item) 
    { 
     var entry = source.Select((x, i) => new { Value = x, Index = i }) 
        .Where(x => object.Equals(x.Value, item)) 
        .FirstOrDefault(); 
     return entry != null ? entry.Index : -1; 
    } 

CopyTo (такой же, как IList<T>.CopyTo, но работает на IEnumerable<T>)

public static void CopyTo<T>(this IEnumerable<T> source, T[] array, int startIndex) 
    { 
     int lowerBound = array.GetLowerBound(0); 
     int upperBound = array.GetUpperBound(0); 
     if (startIndex < lowerBound) 
      throw new ArgumentOutOfRangeException("startIndex", "The start index must be greater than or equal to the array lower bound"); 
     if (startIndex > upperBound) 
      throw new ArgumentOutOfRangeException("startIndex", "The start index must be less than or equal to the array upper bound"); 

     int i = 0; 
     foreach (var item in source) 
     { 
      if (startIndex + i > upperBound) 
       throw new ArgumentException("The array capacity is insufficient to copy all items from the source sequence"); 
      array[startIndex + i] = item; 
      i++; 
     } 
    } 
+2

В .NET 4.0 можно было бы создать структуру списка, которая автоматически удаляла объекты, которые GC'ed, используя «ConditionalWeakTable» для присоединения объектов в списке к другим объектам с финализаторами, выполнит удаление. Обратите внимание, что это не должно делаться с помощью списка с числовой индексацией (так как нет возможности выполнить удаление в потокобезопасном режиме), но это может быть сделано с помощью связанного списка, который выполняет итерации в порядке или обратном порядке создания. Однако я не уверен, в каких случаях активно удалять ссылки по мере их смерти ... – supercat

+0

... было бы лучше, чем подсчет количества элементов, добавленных с момента последней очистки, и сколько было в живых в то время и делать чистку, когда количество предметов, добавленных с последнего, превышает число, которое было тогда тогда (или, альтернативно, каждый раз, когда элемент добавлен, сканируйте несколько элементов для удаления, отслеживая свою позицию в список и перезапуск в начале, когда это необходимо). Такой подход в любой момент времени будет содержать ненужные объекты «WeakReference» в объеме, но число будет ограничено относительно числа, которое было живым на последнем GC. – supercat

+0

@supercat Считается, но, к сожалению, финализаторы поставляются с дополнительными расходами на память + производительность, и они будут работать из фонового потока и, следовательно, требуют, чтобы вы выполняли блокировку или использовали потокобезопасные коллекции ... (больше накладных расходов) –

0

Для любого нуждающегося использовать ConditionalWeakTable в .NET 2.0 или 3.5 есть портировать его здесь: https://github.com/theraot/Theraot/wiki/Features

+0

Привет, Патрик. Просто видел ваш пост. Насколько я читал документы ConditionalWeakTable, не всегда держит ссылки даже в том, что есть сильная ссылка на объект извне. У вас есть другая информация? – msedi