Good implementation of weak dictionary in .Net

前端 未结 7 1866
Happy的楠姐
Happy的楠姐 2020-12-14 16:12

Where can I find good implementation of IDictionary which uses weak references inside?

Dictionary should be holding only weak references to values and e

7条回答
  •  南方客
    南方客 (楼主)
    2020-12-14 17:01

    This will work without the performance problems of the other solutions.

    (It doesn't rely on a "shrink" method being called upon every request in order to manually get rid of dead objects. And these "shrink" methods would have to loop through every item on every call. I do have a "shrink" method, but it is only called when the items would be enumerated anyway.)

    The key to the problem is to use a "holder" object as a value in the ConditionalWeakTable, so that when the key gets dropped, the holder's finalizer will trigger, which removes the key from the "active list" of keys.

    I tested this and it works.

    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 : IDictionary, IDisposable
            where TKey : class
            where TValue : class
        {
            private readonly object locker = new object();
            //private readonly HashSet weakKeySet = new HashSet(new ObjectReferenceEqualityComparer());
            private ConditionalWeakTable keyHolderMap = new ConditionalWeakTable();
            private Dictionary valueMap = new Dictionary(new ObjectReferenceEqualityComparer());
    
    
            private class WeakKeyHolder
            {
                private WeakDictionary outer;
                private WeakReference keyRef;
    
                public WeakKeyHolder(WeakDictionary 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 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 Keys
            {
                get
                {
                    lock(this.locker)
                    {
                        this.manualShrink();
                        return this.valueMap.Keys.Select(k => (TKey) k.Target).ToList();
                    }
                }
            }
    
            public ICollection 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 item)
            {
                this.Add(item.Key, item.Value);
            }
    
            public void Clear()
            {
                lock(this.locker)
                {
                    this.keyHolderMap = new ConditionalWeakTable();
                    this.valueMap.Clear();
                }
            }
    
            public bool Contains(KeyValuePair 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[] array, int arrayIndex)
            {
                ((IDictionary) this.currentDictionary).CopyTo(array, arrayIndex);
            }
    
            public IEnumerator> 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 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 : IEqualityComparer
    {
        public static ObjectReferenceEqualityComparer Default = new ObjectReferenceEqualityComparer();
    
        public bool Equals(T x, T y)
        {
            return ReferenceEquals(x, y);
        }
    
        public int GetHashCode(T obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }
    
    public class ObjectReferenceEqualityComparer : ObjectReferenceEqualityComparer
    {
    }
    
    }
    
        

    提交回复
    热议问题