C# 如何安全地枚举列表<;Weakreference>;没有终结器的阻碍?

C# 如何安全地枚举列表<;Weakreference>;没有终结器的阻碍?,c#,weak-references,C#,Weak References,我的应用程序中有一个WeakReference的静态列表。在某个时刻,我想拍摄此列表中所有当前“活动”对象的快照 代码如下: private static readonly List<WeakReference> myObjects = new List<WeakReference>(); public static MyObject[] CollectObjects() { var list = new List<MyObject>();

我的应用程序中有一个WeakReference的静态列表。在某个时刻,我想拍摄此列表中所有当前“活动”对象的快照

代码如下:

private static readonly List<WeakReference> myObjects = new List<WeakReference>();

public static MyObject[] CollectObjects()
{
    var list = new List<MyObject>();
    foreach (var item in myObjects)
    {
        if (!item.IsAlive)
            continue;
        var obj = item.Target as MyObject;
        list.Add(obj);
    }
    return list.ToArray();
}
public class MyObject
{
    private static readonly object _lockObject = new object();
    WeakReference referenceToThis;
    public MyObject()
    {
        lock (_lockObject)
        {
            referenceToThis = new WeakReference(this);
            myObjects.Add(referenceToThis);
        }
    }
    ~MyObject()
    {
        lock (_lockObject)
        {
            myObjects.Remove(referenceToThis);
        }
    }
}
由于我的代码中没有其他内容涉及列表,因此我的假设是,垃圾收集器正在完成其中一些对象,就像我尝试枚举列表一样

我曾考虑在foreach循环周围添加一个
锁(\u lockObject)
,但我不确定这样的锁会如何影响GC


是否有更好/正确/更安全的方法来枚举weakreference列表?

这可能会损害GC和/或终结器(取决于性能),因为您在快速执行场景中使用的是
锁定
(关键部分)机制

您确实应该将枚举包装在同步块中,但也应该使用implement()模式(而不是
),这对于快速同步块非常有用:

readonly SpinLock sl = new SpinLock();
bool lockTaken = false;

...

try
{
    sl.Enter(ref lockTaken);
    // do your thing
}
finally
{
    if (lockTaken)
    {
        sl.Exit();
    }
}

终结器在整个垃圾收集机制中引入了很大的开销,因此如果可以,最好避免它。在您的情况下,您可以避免它并大大简化您的设计

不要让对象通过终结器检测自己的终结并将自己从该列表中删除,而是将
列表
替换为
WeakCollection

WeakCollection
是一个集合,而不是列表。千万不要在收集足够的地方使用列表

WeakCollection
完全封装了它包含弱引用的事实,这意味着您将其用作常规列表,其接口中没有弱引用

WeakCollection
检测到弱引用已过期时,会自动从自身删除弱引用。(您会发现,将其实现为列表比将其实现为集合要复杂得多,所以再次强调,在集合足够的地方,不要使用列表。)

下面是一个
WeakCollection
的示例实现。注意:我还没有测试过

namespace Whatever
{
    using Sys = System;
    using Legacy = System.Collections;
    using Collections = System.Collections.Generic;

    public class WeakCollection<T> : Collections.ICollection<T> where T : class
    {
        private readonly Collections.List<Sys.WeakReference<T>> list = new Collections.List<Sys.WeakReference<T>>();

        public void                           Add( T item )   => list.Add( new Sys.WeakReference<T>( item ) );
        public void                           Clear()         => list.Clear();
        public int                            Count           => list.Count;
        public bool                           IsReadOnly      => false;
        Legacy.IEnumerator Legacy.IEnumerable.GetEnumerator() => GetEnumerator();

        public bool Contains( T item )
        {
            foreach( var element in this )
                if( Equals( element, item ) )
                    return true;
            return false;
        }

        public void CopyTo( T[] array, int arrayIndex )
        {
            foreach( var element in this )
                array[arrayIndex++] = element;
        }

        public bool Remove( T item )
        {
            for( int i = 0; i < list.Count; i++ )
            {
                if( !list[i].TryGetTarget( out T target ) )
                    continue;
                if( Equals( target, item ) )
                {
                    list.RemoveAt( i );
                    return true;
                }
            }
            return false;
        }

        public Collections.IEnumerator<T> GetEnumerator()
        {
            for( int i = list.Count - 1; i >= 0; i-- )
            {
                if( !list[i].TryGetTarget( out T element ) )
                {
                    list.RemoveAt( i );
                    continue;
                }
                yield return element;
            }
        }
    }
}
名称空间
{
使用Sys=系统;
使用Legacy=System.Collections;
使用集合=System.Collections.Generic;
公共类WeakCollection:Collections.i集合,其中T:class
{
私有只读集合。列表=新集合。列表();
公共作废新增(T项)=>list.Add(新增系统WeakReference(项));
public void Clear()=>list.Clear();
public int Count=>list.Count;
公共bool IsReadOnly=>false;
Legacy.IEnumerator Legacy.IEnumerable.GetEnumerator()=>GetEnumerator();
公共布尔包含(T项)
{
foreach(本文件中的var元素)
if(等于(元素、项目))
返回true;
返回false;
}
public void CopyTo(T[]数组,int arrayIndex)
{
foreach(本文件中的var元素)
数组[arrayIndex++]=元素;
}
公共布尔删除(T项)
{
for(int i=0;i=0;i--)
{
if(!list[i].TryGetTarget(out T元素))
{
列表。删除(i);
继续;
}
收益-收益要素;
}
}
}
}

是否尝试将
true
作为第二个参数传递给
WeakReference
?还有一个可以帮助的现有参数不,我必须承认我不理解这个参数的作用。
~MyObject()
在C#中,你不知道什么时候调用它,如果
myObjects
引用你的对象,它就不能为GC释放,所以可能
~MyObject()
从未工作过?没有在foreach循环中使用锁是一个明显的错误,您必须修复它。请注意,匆忙销毁弱引用通常是不明智的。例如,您也可以在迭代集合时执行此操作,但需要的是for而不是foreach。锁定插入和移除而不是foreach显然对您的问题没有影响。但我不确定在这里使用lock是不是一个好主意:如果你手动检查“有效性”,为什么还要麻烦自动从列表中删除死物呢,我不确定将维护逻辑放在GetEnumerator中并让GetEnumerator以相反的顺序返回列表对我来说有点奇怪。这是一个收藏。因此,它是无序的。因此,可以按任何顺序返回项目。但如果你真的无法忍受,你可以改变它,它只会稍微复杂一点。至于枚举器中的维护逻辑,缓存就是这样工作的,这本质上就是一个缓存。我明白你的意思。我认为这是一个更好的方法,再次感谢分享!