.Net中弱引用字典的良好实现

36

我应该在哪里找到使用弱引用的IDictionary的良好实现?

Dictionary应该仅保存对值的弱引用,并最终清理不可访问的引用。

还是我自己写比较好?


我认为如果你只是在字典中使用WeakReference作为值,那么你将会得到你所需要的。另请参阅此链接,该链接完全相同以构建缓存http://msdn.microsoft.com/en-us/library/system.weakreference.aspx - Henri
http://blogs.msdn.com/b/nicholg/archive/2006/06/04/617466.aspx - Mark
5
虽然它不是一个IDictionary,但ConditionalWeakTable正是我在Google搜索时真正寻找的东西。感谢这个答案 - default.kramer
这仅适用于.NET 4.0。 - Valentin Simonov
我必须说,ConditionalWeakTable非常棒。谢谢! - rikoe
8个回答

44

ConditionalWeakTable类使用弱键,只要在表格之外没有对键的其他引用存在,就会自动删除键/值条目。


6
需要注意的是,这个类使用的是ReferenceEquals而不是GetHashCodeEquals来进行相等性检查。请查看https://dev59.com/gGoy5IYBdhLWcg3wnPOJ#8441180以获得更全面的讨论。 - larsmoa
30
OP正在寻找一个具有弱值的弱字典。.NET中的ConditionalWeakTable类是一个具有弱键的弱字典。因此,我认为这不是一个正确的答案。 - J D
4
但这正是我正在寻找的:P 所以感谢您确认了这一点! - schwarz

7

你需要自己编写代码。这个过程相对简单,实现 IDictionary<T,T> 接口,然后将实际值存储为 WeakReferences<T>。你可以使用 TryGetTarget 在添加/选择时检查值是否仍然存在。

public class WeakDictionary <TKey,TValue> : IDictionary<TKey,TValue>
    where TValue : class
{
    private readonly Dictionary<TKey,WeakReference<TValue>> innerDictionary = new Dictionary<TKey,WeakReference<TValue>>();
    
    
    public TValue Index[ TKey key ]
    {
        get
        {
            // Use .TryGetTarget instead of .IsAlive and .Target
            if (this.innerDictionary.TryGetValue(key, out WeakReference<TValue> wf) && wf.TryGetTarget(out TValue value))
            {
                return value;
            }

            return null;
        }
        
    }
    
    private void Cull()
    {
        var deadKeys = this.innerDictionary.Where(kvp => kvp.Value.IsAlive).Select(kvp => kvp.Key).ToList();

        foreach (var key in deadKeys)
        {
            _ = this.innerDictionary.TryRemove(key);
        }
    }
}

11
请注意,在 reference.IsAlivereference.Target 之间可能会发生垃圾回收,因此这种解决方案容易出现竞态条件。 - rr-
3
在检查IsAlive之前在自己的堆栈上放置目标足够简单,这可以解决那个竞态问题。 - Rhys Bevilaqua
3
为什么你不使用WeakReference.TryGetValue呢? - TamusJRoyce
3
好的,像其他人建议的一样,使用TryGetTarget和WeakReference<T>。 - Demetris Leptos

4
仅仅持有一个WeakReference对象的字典存在一个问题,即没有办法在不枚举整个字典的情况下从字典中移除任何目标超出作用域的WeakReference对象。
如果WeakReference能够包含一个委托,当主要目标超出作用域时将被调用,那将非常有帮助。据我所知,没有办法实现这一点。如果您不介意为存储在“弱字典”中的对象添加另一个字段和一些代码,我建议创建一个我称之为“Finasposer”对象,其唯一字段是MethodInvoker; 当处置时,MethodInvoker应该被清空;终结器应该Interlocked.Exchange() MethodInvoker为null,并且-如果其旧值非null-则调用它。要写入字典中的对象应创建一个新的Finasposer对象,其中包含一个委托,该委托将导致在方便时从字典中删除键。
注意,终结器或由其调用的任何委托都不应直接操作字典,也不应执行需要获取锁的任何操作。如果Finasposer持有一个委托,则该委托本身在Finalize执行时保证有效,但附加到委托的对象及其引用的任何对象可能处于意外状态。然而,对于Finasposer-called方法将超出作用域的对象的引用添加到链接列表中应该是安全的。字典的Add、Remove和其他方法可以轮询链接列表,以查看其中是否有任何WeakReferences已死亡并需要清除。

在对象被回收后有一种方法可以进行通知。ConditionalWeakTable 可以帮助实现这个功能。详情请参考我的博客文章WeakTable - Vladimir Nesterovsky
1
我只想说,当我读到这个答案第二段的前两句话时,我就知道这是supercat了(在我向下滚动以确认之前)。独特的签名风格和语气。在我过去12年阅读过的所有回答者中都很突出。深思熟虑,尤其是探索性的。我从来没有不喜欢supercat的回答。我不想把这变成一个粉丝的事情,我只是真的欣赏这种回答方式。 - William
@N73k:我认为当我写这个答案时ConditionalWeakTable还没有出现。至于Finasposer类,基本思想是封装终结逻辑在一个通用类中,当特定对象不存在时运行指定的代码。请注意,Finalize方法实际上并不表示对象何时被垃圾回收,而是当对象树中某处注册了Finalize方法时,对象将会被垃圾回收。找出对象何时实际上被垃圾回收... - supercat
在.NET Core 2.0+中,ConditionalWeakTable实现了IEnumerable<...> - Vlad
@supercat:我想你可以在这里提交一个问题:https://github.com/dotnet/docs - Vlad
显示剩余6条评论

4

这种方法可以避免其他解决方案的性能问题。

(不需要在每次请求时调用“收缩”方法来手动清除无用对象。而且这些“收缩”方法需要在每次调用时循环处理每个项目。虽然我有一个“收缩”方法,但只在枚举项目时才会调用。)

解决问题的关键是在ConditionalWeakTable中使用一个“持有者”对象作为值,这样当键被删除时,持有者的终结器会触发,将该键从“活动列表”中移除。

我进行了测试,它可以正常工作。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Util
{
    public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDisposable
        where TKey : class
        where TValue : class
    {
        private readonly object locker = new object();
        //private readonly HashSet<WeakReference> weakKeySet = new HashSet<WeakReference>(new ObjectReferenceEqualityComparer<WeakReference>());
        private ConditionalWeakTable<TKey, WeakKeyHolder> keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
        private Dictionary<WeakReference, TValue> valueMap = new Dictionary<WeakReference, TValue>(new ObjectReferenceEqualityComparer<WeakReference>());


        private class WeakKeyHolder
        {
            private WeakDictionary<TKey, TValue> outer;
            private WeakReference keyRef;

            public WeakKeyHolder(WeakDictionary<TKey, TValue> outer, TKey key)
            {
                this.outer = outer;
                this.WeakRef = new WeakReference(key);
            }

            public WeakReference WeakRef { get; private set; }

            ~WeakKeyHolder()
            {
                this.outer?.onKeyDrop(this.WeakRef);  // Nullable operator used just in case this.outer gets set to null by GC before this finalizer runs. But I haven't had this happen.
            }
        }

        private void onKeyDrop(WeakReference weakKeyRef)
        {
            lock(this.locker)
            {
                if (!this.bAlive)
                    return;

                //this.weakKeySet.Remove(weakKeyRef);
                this.valueMap.Remove(weakKeyRef);
            }
        }

        
    // The reason for this is in case (for some reason which I have never seen) the finalizer trigger doesn't work
    // There is not much performance penalty with this, since this is only called in cases when we would be enumerating the inner collections anyway.
        private void manualShrink()
        {
            var keysToRemove = this.valueMap.Keys.Where(k => !k.IsAlive).ToList();

            foreach (var key in keysToRemove)
                valueMap.Remove(key);
        }

        private Dictionary<TKey, TValue> currentDictionary
        {
            get
            {
                lock(this.locker)
                {
                    this.manualShrink();
                    return this.valueMap.ToDictionary(p => (TKey) p.Key.Target, p => p.Value);
                }
            }
        }

        public TValue this[TKey key]
        {
            get
            {
                if (this.TryGetValue(key, out var val))
                    return val;

                throw new KeyNotFoundException();
            }

            set
            {
                this.set(key, value, isUpdateOkay: true);
            }
        }

        private bool set(TKey key, TValue val, bool isUpdateOkay)
        {
            lock (this.locker)
            {
                if (this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
                {
                    if (!isUpdateOkay)
                        return false;

                    this.valueMap[weakKeyHolder.WeakRef] = val;
                    return true;
                }

                weakKeyHolder = new WeakKeyHolder(this, key);
                this.keyHolderMap.Add(key, weakKeyHolder);
                //this.weakKeySet.Add(weakKeyHolder.WeakRef);
                this.valueMap.Add(weakKeyHolder.WeakRef, val);

                return true;
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                lock(this.locker)
                {
                    this.manualShrink();
                    return this.valueMap.Keys.Select(k => (TKey) k.Target).ToList();
                }
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                lock (this.locker)
                {
                    this.manualShrink();
                    return this.valueMap.Select(p => p.Value).ToList();
                }
            }
        }

        public int Count
        {
            get
            {
                lock (this.locker)
                {
                    this.manualShrink();
                    return this.valueMap.Count;
                }
            }
        }

        public bool IsReadOnly => false;

        public void Add(TKey key, TValue value)
        {
            if (!this.set(key, value, isUpdateOkay: false))
                throw new ArgumentException("Key already exists");
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            this.Add(item.Key, item.Value);
        }

        public void Clear()
        {
            lock(this.locker)
            {
                this.keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
                this.valueMap.Clear();
            }
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            WeakKeyHolder weakKeyHolder = null;
            object curVal = null;

            lock (this.locker)
            {
                if (!this.keyHolderMap.TryGetValue(item.Key, out weakKeyHolder))
                    return false;

                curVal = weakKeyHolder.WeakRef.Target;
            }

            return (curVal?.Equals(item.Value) == true);
        }

        public bool ContainsKey(TKey key)
        {
            lock (this.locker)
            {
                return this.keyHolderMap.TryGetValue(key, out var weakKeyHolder);
            }
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            ((IDictionary<TKey, TValue>) this.currentDictionary).CopyTo(array, arrayIndex);
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return this.currentDictionary.GetEnumerator();
        }

        public bool Remove(TKey key)
        {
            lock (this.locker)
            {
                if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
                    return false;

                this.keyHolderMap.Remove(key);
                this.valueMap.Remove(weakKeyHolder.WeakRef);

                return true;
            }
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            lock (this.locker)
            {
                if (!this.keyHolderMap.TryGetValue(item.Key, out var weakKeyHolder))
                    return false;

                if (weakKeyHolder.WeakRef.Target?.Equals(item.Value) != true)
                    return false;

                this.keyHolderMap.Remove(item.Key);
                this.valueMap.Remove(weakKeyHolder.WeakRef);

                return true;
            }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            lock (this.locker)
            {
                if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
                {
                    value = default(TValue);
                    return false;
                }
                
                value = this.valueMap[weakKeyHolder.WeakRef];
                return true;
            }
        }

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

        private bool bAlive = true;

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected void Dispose(bool bManual)
        {
            if (bManual)
            {
                Monitor.Enter(this.locker);

                if (!this.bAlive)
                    return;
            }
            
            try
            {
                this.keyHolderMap = null;
                this.valueMap = null;
                this.bAlive = false;
            }
            finally
            {
                if (bManual)
                    Monitor.Exit(this.locker);
            }
        }

        ~WeakDictionary()
        {
            this.Dispose(false);
        }
    }


public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
{
    public static ObjectReferenceEqualityComparer<T> Default = new ObjectReferenceEqualityComparer<T>();

    public bool Equals(T x, T y)
    {
        return ReferenceEquals(x, y);
    }

    public int GetHashCode(T obj)
    {
        return RuntimeHelpers.GetHashCode(obj);
    }
}

public class ObjectReferenceEqualityComparer : ObjectReferenceEqualityComparer<object>
{
}

}

惊讶这个帖子没有更多的点赞!非常聪明。唯一的缺点是键必须是类。 - Emperor Eto

2
似乎所有现有的答案都是:
  • 使用对键进行弱引用而不是对值进行弱引用的字典实现,或者
  • 依赖于一个需要定期调用以清理旧引用的显式“清除”操作。
我已经实现了一个版本,将弱引用应用于字典的值,立即删除垃圾收集值的条目。 仓库: bhaeussermann/weak-dictionary NuGet软件包: BernhardHaus.Collections.WeakDictionary 文章: 在.NET中创建弱字典 实现说明:
  • ConditionalWeakTable类保持对其键的弱引用,但我们希望弱引用在值上。因此,我们使用我们字典的值作为ConditionalWeakTable的键。
  • 当键被垃圾收集时,ConditionalWeakTable会删除相关条目,这也导致value被垃圾收集(假设没有对该值的其他引用)。这会导致值对象的解构器被调用。我们利用这一点,以便立即从内部字典中删除相应的条目。
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    where TValue : class
{
    private readonly Dictionary<TKey, WeakReference> internalDictionary = new Dictionary<TKey, WeakReference>();
    private readonly ConditionalWeakTable<TValue, Finalizer> conditionalWeakTable = new ConditionalWeakTable<TValue, Finalizer>();

    public TValue this[TKey key]
    {
        get => (TValue)internalDictionary[key].Target;
        set
        {
            Remove(key);
            Add(key, value);
        }
    }

    public ICollection<TKey> Keys => internalDictionary.Keys;

    public ICollection<TValue> Values => internalDictionary.Values.Select(r => (TValue)r.Target).ToArray();

    public int Count => internalDictionary.Count;

    public bool IsReadOnly => false;

    public void Add(TKey key, TValue value)
    {
        internalDictionary.Add(key, new WeakReference(value));
        var finalizer = new Finalizer(key);
        finalizer.ValueFinalized += k => Remove(k);
        conditionalWeakTable.Add(value, finalizer);
    }

    public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);

    // Implement the remaining IDictionary<,> methods to simply relay the method call to internalDictionary.
    // See https://github.com/bhaeussermann/weak-dictionary/blob/main/src/WeakDictionary/WeakDictionary.cs for the complete implementation.
    // ...

    private sealed class Finalizer
    {
        private readonly TKey valueKey;

        public Finalizer(TKey valueKey)
        {
            this.valueKey = valueKey;
        }

        ~Finalizer()
        {
            ValueFinalized?.Invoke(valueKey);
        }

        public event ValueFinalizedDelegate ValueFinalized;
    }

    private delegate void ValueFinalizedDelegate(TKey valueKey);
}

这是一个 NUnit 测试,用于展示当一个条目的值不再被引用时,它将被移除。
这已经在 .NET Framework 和 .NET Core 上进行了测试。请注意,在使用 .NET Core 时,需要开启编译器优化,并且不能附加调试器才能看到其工作。参见此帖子
[Test]
public void WeakDictionary()
{
    var v1 = new ValueType();
    var v2 = new ValueType();
    var v3 = new ValueType();

    var dictionary = new WeakDictionary<int, ValueType>
    {
        { 1, v1 },
        { 2, v2 },
        { 3, v3 }
    };

    var weakReference = new WeakReference(v2);
    v2 = null;

    // Loop forces non-referenced values to be garbage collected on .NET Core (see https://stackoverflow.com/a/68836653/359765)
    for (int i = 0; i < 1; i++)
    {
        GC.Collect();
    }

    Assert.IsFalse(weakReference.IsAlive);
    CollectionAssert.AreEquivalent(new int[] { 1, 3 }, dictionary.Keys, "Unexpected keys after garbage collection.");

    // These references to v1 and v2 prevent the compiler from adding optimizations that will cause v1 and v2 to be garbage collected.
    v1.ToString();
    v3.ToString();
}

private class ValueType { }

1
感谢您的反馈!我在GitHub存储库中添加了文件链接。代码也可作为NuGet包使用,我也将其添加到答案中。 - bgh
关于CopyTo方法,在C#语言中,允许子类实现公共接口方法并将这些实现的可见性降低为私有,这似乎完全没有意义,尤其是当将实现类强制转换回接口类型时,可以绕过相同减少可见性的限制,使方法再次变为公共。.NET框架本身为什么要编写这样的代码呢? - busitech
我想知道你是否认为这仍然存在原始问题,即从设计角度来看,这种代码是否属于.NET框架。在你的意见中,框架或语言开发人员使用更清晰的设计是否对社区有好处?人们会认为,在通用和非通用接口中重用相同的方法名称是为了增加简单性,但是我们仍然要处理多个重叠名称和重复接口的复杂性,以至于无法轻松地使用该类... : ) - busitech
我不确定“更清晰的设计”会是什么样子。我猜可能可以避免接口具有重叠的方法名称。然而,假设“CopyTo”对于两个接口来说都是理想的方法名称,这意味着选择次优名称。此外,任何处理ICollection类型对象的代码都应该能够使用名为“CopyTo”的方法,并且对于存在具有相同名称的不同接口这一事实毫不知情。 - bgh
我认为值得注意的是IDictionary<T, U>接口仅扩展了泛型类型ICollection<KeyValuePair<T, U>>。这意味着在任何使用IDictonary<T, U>类型引用的代码中,方法名称的歧义不存在,因为您将自动调用ICollection<KeyValuePair<T, U>>类型的CopyTo()方法。这作为引用接口类型IDictionary<T, U>而不是类类型Dictionary<T, U>的动机,这本身就是更好的编程实践。 - bgh
显示剩余2条评论

2

这是我实现的一个并发弱(值)字典版本:

public class WeakConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
    private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _internalDictionary =
        new ConcurrentDictionary<TKey, WeakReference<TValue>>();

    public TValue this[TKey key]
    {
        get
        {
            if (_internalDictionary.TryGetValue(key, out var weakReference) &&
                weakReference.TryGetTarget(out var value))
                return value;

            return null;
        }
        set
        {
            _internalDictionary.TryAdd(key, new WeakReference<TValue>(value));
        }
    }

    public ICollection<TKey> Keys => _internalDictionary.Keys;

    public ICollection<TValue> Values => _internalDictionary.Values
        .Select(_ => _.GetTarget())
        .Where(_ => _ != null)
        .ToList();

    public int Count => _internalDictionary.Count;

    public bool IsReadOnly => false;

    public void Add(TKey key, TValue value)
    {
        Purge();
        if (!_internalDictionary.TryAdd(key, new WeakReference<TValue>(value)))
        {
            throw new InvalidOperationException("Key already existing");
        }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException();
    }

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

    public bool Contains(KeyValuePair<TKey, TValue> item) => _internalDictionary.TryGetValue(item.Key, out var weakReference) &&
                weakReference.GetTarget() == item.Value;

    public bool ContainsKey(TKey key) => _internalDictionary.TryGetValue(key, out var weakReference) &&
                weakReference.IsAlive();

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        Purge();
        _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value != null)
            .ToList()
            .CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        Purge();
        return _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value != null)
            .GetEnumerator();
    }

    public bool Remove(TKey key)
    {
        return _internalDictionary.TryRemove(key, out var weakReference);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        throw new NotSupportedException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        value = null;
        if (_internalDictionary.TryGetValue(key, out var weakReference))
        {
            value = weakReference.GetTarget();
        }

        return value != null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        Purge();
        return GetEnumerator();
    }

    public void Purge()
    {
        foreach (var itemToRemove in _internalDictionary
            .Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
            .Where(_ => _.Value == null))
        {
            _internalDictionary.TryRemove(itemToRemove.Key, out var weakReference);
        }
    }
}

public static class WeakReferenceExtensions
{
    public static bool IsAlive<T>([NotNull] this WeakReference<T> weakReference) where T : class =>
        weakReference.TryGetTarget(out var target);

    public static T GetTarget<T>([NotNull] this WeakReference<T> weakReference, T defaultValue = default(T)) where T : class
    {
        if (!weakReference.TryGetTarget(out T target))
            return defaultValue;

        return target;
    }
}

还有一个测试证明引用值实际上被丢弃了:

    [TestMethod]
    public void TestWeakDictionary()
    {
        var weakDict = new WeakConcurrentDictionary<string, TestItem>();

        {
            var testItem = new TestItem();
            weakDict.Add("testitem", testItem);

            Assert.AreEqual(1, weakDict.Count);
            Assert.AreSame(testItem, weakDict["testitem"]);
        }

        GC.Collect();
        Assert.IsNull(weakDict["testitem"]);
        weakDict.Purge();
        Assert.AreEqual(0, weakDict.Count);
    }

一些注意事项:

  1. Property Keys返回所有键,即使是那些值已被收集的条目,但Values始终返回实时的非空对象。
  2. this[key]可能返回null
  3. 您可以选择调用Purge来清除已收集值的条目
  4. 在发布模式下编译和执行时,Test可以正常工作

1

拥有对值的弱引用是一回事,但我发现字典键也可能是内存泄漏的源头。这是一个仅包含对键的弱引用的基本实现:

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
如果无法使用身份比较,则ConditionalWeakTable不是一个选项。
在这种情况下,我敢建议我们的实现WeakTable.cs,以及我们在博客中的描述WeakTable

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