在CLR中是否有一种实现WeakList或WeakCollection(类似于WeakReference)的方法?

25

使用 List<WeakReference> 不能满足我的要求。我希望 WeakReferences 在它们引用的对象被垃圾回收时能够自动从列表中 移除

ConditionalWeakTable<TKey,TValue> 也无法满足我的要求,因为尽管其键和值是弱引用且可回收的,但您无法枚举它们!


很有趣的是,到目前为止,两个答案都有一些不完全自动化的清除步骤。我需要花一些时间来思考它,但即使它不是完全自动化的,它实际上可能已经足够满足我的需求了。 - Tim Lovell-Smith
1
清除最自然的方式是在枚举期间完成。唯一的其他选择是定期清除,这种情况下解决方案更像是“缓存”而不是“弱列表”。WeakReference不应用于缓存; 有更好的解决方案(例如System.Runtime.Caching)。 - Stephen Cleary
1
感谢您对System.Runtime.Caching的有趣建议。但是我在考虑这个问题时有一个特定的应用程序,并且我可以看到一些阻抗不匹配-1)我不需要或不想使用字符串键来获取项目,我只想能够按需迭代它们。 2)如果项目仅因为被垃圾回收而离开缓存,而不是出于其他杂项原因(例如缓存使用过多内存),那么我可能会更加满意。 - Tim Lovell-Smith
这是如何做到的。请参考此StackOverflow答案。 - N73k
4个回答

9
您可以轻松实现一个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>

的作用是复制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中,可以设计一个列表结构,通过使用ConditionalWeakTable将列表中的对象附加到具有终结器的其他对象上,以自动删除被GC回收的对象。请注意,不应该使用数字索引列表来完成此操作(因为没有办法以线程安全的方式完成删除),但是可以使用按创建顺序或反向顺序迭代事物的链接列表来完成此操作。然而,我不确定在哪些情况下主动删除成为死亡的引用... - supercat
比起记录自上次清除以来添加的项数、当时还有多少活着的项数,然后在添加的项数超过当初活着的项数时才进行一次清除(或者每次添加一个项目就扫描几个项目进行删除,跟踪列表中的位置,并在适当时重新开始)。这样的方法在任何时候都会保留一些无用的WeakReference对象,但相对于上一次垃圾回收时存活的数量而言,其数量是有限的。 - supercat
@supercat 考虑过,但不幸的是,最终器会带来额外的内存和性能成本,它们将在后台线程中运行,因此需要进行锁定或使用线程安全集合... (更多开销) - Tim Lovell-Smith
还存在一个默认的通用实现WeakReference<T> https://msdn.microsoft.com/en-us/library/gg712738%28v=vs.110%29.aspx - chtenb
1
@ChieltenBrinke,确实,但是当我写这个答案的时候它还不存在 ;) - Thomas Levesque

9
我同意实现一个WeakList<T>是可能的,但我认为这并不是非常容易。您可以在此处使用我的实现WeakCollection<T>类依赖于WeakReference<T>,而后者又依赖于SafeGCHandle

@stephen-cleary - 这似乎不在您最新的源代码中,因此我很好奇您如何使用最新的CLR来解决这个问题。 - Mike-E
2
@Mike-EEE:对于弱引用,我使用Connected Properties。它们不支持枚举,但我从未需要过这种能力。 - Stephen Cleary
2
这个“here”链接已经失效了。 - blackboxlogic
链接 http://nitokitchensink.codeplex.com/SourceControl/changeset/view/51391#1006413 已经失效。存档链接没有包含源代码或二进制文件。 - dbc
链接已经修复。 - Stephen Cleary

0

嗨Patrick。刚看到你的帖子。就我阅读ConditionalWeakTable文档所知,它并不一定会在对象外部存在强引用时保留引用。你有其他信息吗? - msedi

-3
使用java.util.WeakHashMap来存储对象,键可以是任何虚拟对象。这样你就可以得到WeakSet的功能了,但是需要注意的是,该Map没有顺序。

这个问题被标记为 .net,所以 Java 无法帮助。 - blackboxlogic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接