Is there a way to do a WeakList or WeakCollection (like WeakReference) in CLR?

10,445

Solution 1

I agree that implementing a WeakList<T> is possible, but I don't think it's exactly easy. You're welcome to use my implementation here. The WeakCollection<T> class depends on WeakReference<T>, which in turn depends on SafeGCHandle.

Solution 2

You could easily implement a WeakList<T> class, which would wrap a List<WeakReference>.

There is no way to automatically remove objects when they are garbage collected, because it's not possible to detect when this happens. However, you could remove "dead" (garbage collected) objects when you encounter them, by checking the WeakReference.IsAlive property. However, I wouldn't recommend this approach, because it could lead to confusing behavior from the client's point of view. Instead, I would recommend implementing a Purge method to remove dead entries, that you would call explicitly.

Here's a sample implementation :

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

This class uses the following classes and extension methods :

WeakReference<T> (just a strongly typed wrapper around 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 (same as IList<T>.IndexOf, but works on a 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 (same as IList<T>.CopyTo, but works on a 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++;
        }
    }
Share:
10,445
Tim Lovell-Smith
Author by

Tim Lovell-Smith

Updated on June 06, 2022

Comments

  • Tim Lovell-Smith
    Tim Lovell-Smith about 2 years

    Using a List<WeakReference> will not work as I want. What I want is for WeakReferences to be automatically removed from the list whenever the object they reference is garbage collected.

    ConditionalWeakTable<TKey,TValue> does not satisfy me either, because although its keys and values are weakly referenced and collectable, you cannot enumerate them!

  • supercat
    supercat almost 12 years
    It would be possible in .net 4.0 to design a list structure which would automatically remove objects that are GC'ed by using a ConditionalWeakTable to attach the objects in the list to other objects with finalizers would perform the removal. Note that this should not be done with a numerically-indexed list (since there's no way to accomplish removal in thread-safe fashion) but it could be done with a linked list that iterates things in order or reverse order of creation. I'm not sure, though, in what cases actively removing references as they become dead...
  • supercat
    supercat almost 12 years
    ...would be better than keeping a count of how many items have been added since the last purge, and how many were alive at that time, and doing a purge when the number of items added since the last one exceeds the number that were alive then (or, alternatively, every time an item is added, scan a few items for removal, keeping track of one's position in the list and restarting at the beginning when appropriate). Such approach would at any given time keep some WeakReference objects needlessly in scope, but the number would be bounded relative to the number that were alive as of the last GC.
  • Tim Lovell-Smith
    Tim Lovell-Smith almost 9 years
    @supercat Considered, but unfortunately finalizers come with extra memory+performance costs and they will run from a background thread and thereby require you to do locking or use thread safe collections... (more overheads)
  • Mike-E
    Mike-E over 8 years
    @stephen-cleary - this doesn't appear to be in your most recent source, so I am curious on how you are approaching this problem with the latest CLR.
  • Stephen Cleary
    Stephen Cleary over 8 years
    @Mike-EEE: For ephemerons, I use Connected Properties. They don't support enumeration, but I've never needed that capability.
  • chtenb
    chtenb over 8 years
    There also exists a default generic implementation for WeakReference<T> msdn.microsoft.com/en-us/library/gg712738%28v=vs.110%29.aspx
  • Thomas Levesque
    Thomas Levesque over 8 years
    @ChieltenBrinke, indeed, but it didn't exist when I wrote this answer ;)
  • msedi
    msedi over 6 years
    Hi Patrick. Just saw your post. As far as I read the docs of the ConditionalWeakTable is does not keep necessarily keep the references even is there is a strong reference to the object from the outside. Do you have other informations?
  • blackboxlogic
    blackboxlogic almost 3 years
    The question is tagged .net, so java doesn't help.
  • blackboxlogic
    blackboxlogic almost 3 years
    The "here" link is dead.