| File: MS\Internal\WeakObjectHashtable.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; // This is a variant of WeakHashtable that works when the keys are value-types. // In particular: // 1. Do not create weak references to a value-type key. That makes no sense - // the GC system doesn't manage the key itself, but only boxes holding the // key. The lifetime of a particular box is irrelevant, and the "lifetime" // concept doesn't apply to a value-type. // 2. Use value-semantics in equality tests. (Object.Equals, not Object.ReferenceEquals). // Reference-semantics tests whether the two boxes are the same, which // is largely a coincidence of how the boxes arrived here. // 3. A few small perf or style improvements over the WeakHashtable code. namespace MS.Internal { /// <devdoc> /// This is a hashtable that stores object keys as weak references. /// It monitors memory usage and will periodically scavenge the /// hash table to clean out dead references. /// </devdoc> internal sealed class WeakObjectHashtable : Hashtable, IWeakHashtable { private static IEqualityComparer _comparer = new WeakKeyComparer(); private long _lastGlobalMem; private int _lastHashCount; internal WeakObjectHashtable() : base(_comparer) { } /// <devdoc> /// Override of Item that wraps a weak reference around the /// key and performs a scavenge. /// </devdoc> public void SetWeak(object key, object value) { ScavengeKeys(); WrapKey(ref key); this[key] = value; } private void WrapKey(ref object key) { if (key != null && !key.GetType().IsValueType) { key = new EqualityWeakReference(key); } } public object UnwrapKey(object key) { EqualityWeakReference keyRef = key as EqualityWeakReference; return (keyRef != null) ? keyRef.Target : key; } /// <devdoc> /// This method checks to see if it is necessary to /// scavenge keys, and if it is it performs a scan /// of all keys to see which ones are no longer valid. /// To determine if we need to scavenge keys we need to /// try to track the current GC memory. Our rule of /// thumb is that if GC memory is decreasing and our /// key count is constant we need to scavenge. We /// will need to see if this is too often for extreme /// use cases like the CompactFramework (they add /// custom type data for every object at design time). /// </devdoc> private void ScavengeKeys() { int hashCount = Count; if (hashCount == 0) { return; } if (_lastHashCount == 0) { _lastHashCount = hashCount; return; } long globalMem = GC.GetTotalMemory(false); if (_lastGlobalMem == 0) { _lastGlobalMem = globalMem; return; } long memDelta = globalMem - _lastGlobalMem; long hashDelta = hashCount - _lastHashCount; if (memDelta < 0 && hashDelta >= 0) { // Perform a scavenge through our keys, looking // for dead references. ArrayList cleanupList = null; foreach (object o in Keys) { EqualityWeakReference wr = o as EqualityWeakReference; if (wr != null && !wr.IsAlive) { if (cleanupList == null) { cleanupList = new ArrayList(); } cleanupList.Add(wr); } } if (cleanupList != null) { foreach (object o in cleanupList) { Remove(o); } } } _lastGlobalMem = globalMem; _lastHashCount = hashCount; } private class WeakKeyComparer : IEqualityComparer { bool IEqualityComparer.Equals(object x, object y) { if (x == null) { return y == null; } if (y == null || x.GetHashCode() != y.GetHashCode()) { return false; } if (object.ReferenceEquals(x, y)) { return true; } EqualityWeakReference wX, wY; if ((wX = x as EqualityWeakReference) != null) { x = wX.Target; if (x == null) { // if a reference-type key has been GC'd, the weak-ref // wrapper can only match itself. We've already checked // that via ReferenceEquals. return false; } } if ((wY = y as EqualityWeakReference) != null) { y = wY.Target; if (y == null) { // if a reference-type key has been GC'd, the weak-ref // wrapper can only match itself. We've already checked // that via ReferenceEquals. return false; } } return object.Equals(x, y); } int IEqualityComparer.GetHashCode(object obj) { return obj.GetHashCode(); } } /// <devdoc> /// A wrapper of WeakReference that overrides GetHashCode and /// Equals so that the weak reference returns the same equality /// semantics as the object it wraps. This will always return /// the object's hash code and will return True for a Equals /// comparison of the object it is wrapping. If the object /// it is wrapping has finalized, Equals always returns false. /// </devdoc> internal sealed class EqualityWeakReference { private int _hashCode; private WeakReference _weakRef; internal EqualityWeakReference(object o) { _weakRef = new WeakReference(o); _hashCode = o.GetHashCode(); } public bool IsAlive { get { return _weakRef.IsAlive; } } public object Target { get { return _weakRef.Target; } } public override bool Equals(object o) { if (o == null) { return false; } if (o.GetHashCode() != _hashCode) { return false; } if (o == this || object.ReferenceEquals(o, Target)) { return true; } return false; } public override int GetHashCode() { return _hashCode; } } } }