From 714ac1c530479fbd998ebe5204852b12a96228c8 Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Fri, 17 Jan 2020 22:37:01 -0600 Subject: [PATCH] Added custom implementation of ConditionalWeakTable for .NET 3 --- Net3-Proxy/CompilerServices.cs | 82 ++++++++++++++++++++++++ Net3-Proxy/Net3-Proxy.csproj | 1 + Net3-Proxy/WeakReference.cs | 110 +++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 Net3-Proxy/WeakReference.cs diff --git a/Net3-Proxy/CompilerServices.cs b/Net3-Proxy/CompilerServices.cs index ed10182c..f37a0787 100644 --- a/Net3-Proxy/CompilerServices.cs +++ b/Net3-Proxy/CompilerServices.cs @@ -5,5 +5,87 @@ using System.Text; namespace System.Runtime.CompilerServices { + public sealed class ConditionalWeakTable where TKey : class where TValue : class + { + private readonly Dictionary, TValue> items = new Dictionary, TValue>(); + private readonly object _lock = new object(); + private sealed class KeyComparer : IEqualityComparer> + { + public bool Equals(WeakReference x, WeakReference y) + => x.TryGetTarget(out var keyX) && y.TryGetTarget(out var keyY) && ReferenceEquals(keyX, keyY); + + public int GetHashCode(WeakReference obj) + => obj.TryGetTarget(out var key) ? key.GetHashCode() : 0; + } + + private static WeakReference WeakRef(TKey key) + => new WeakReference(key); + + private sealed class GCTracker + { + public static event Action OnGC; + private static readonly WeakReference tracker = new WeakReference(new GCTracker()); + ~GCTracker() + { + OnGC?.Invoke(); + if (!AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted) + tracker.SetTarget(new GCTracker()); + } + } + + public void Add(TKey key, TValue value) + { + lock (_lock) + items.Add(WeakRef(key), value); + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (key == null) + throw new ArgumentException("Null key", nameof(key)); + + value = null; + lock (_lock) + return items.TryGetValue(WeakRef(key), out value); + } + + public delegate TValue CreateValueCallback(TKey key); + + public TValue GetValue(TKey key, CreateValueCallback createValueCallback) + { + if (createValueCallback == null) + throw new ArgumentException("Null create delegate", nameof(createValueCallback)); + + lock (_lock) + { + if (TryGetValue(key, out var value)) + return value; + else + { + value = createValueCallback(key); + Add(key, value); + return value; + } + } + } + + public TValue GetOrCreateValue(TKey key) + => GetValue(key, k => Activator.CreateInstance()); + + public bool Remove(TKey key) + => items.Remove(WeakRef(key)); + + public ConditionalWeakTable() + => GCTracker.OnGC += OnGC; + ~ConditionalWeakTable() + => GCTracker.OnGC -= OnGC; + + private void OnGC() + { + // on each GC, we want to clear the entire set of empty keys + var nullWeakRef = WeakRef(null); + while (items.Remove(nullWeakRef)) ; // just loop + } + } } diff --git a/Net3-Proxy/Net3-Proxy.csproj b/Net3-Proxy/Net3-Proxy.csproj index 21a7bccd..f98daa50 100644 --- a/Net3-Proxy/Net3-Proxy.csproj +++ b/Net3-Proxy/Net3-Proxy.csproj @@ -52,6 +52,7 @@ + diff --git a/Net3-Proxy/WeakReference.cs b/Net3-Proxy/WeakReference.cs new file mode 100644 index 00000000..3520c47d --- /dev/null +++ b/Net3-Proxy/WeakReference.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; +using System.Text; + +namespace System +{ + // this is literally jus8t the decompilation of Mono's .NET 4 implementation + + /// Represents a typed weak reference, which references an object while still allowing that object to be reclaimed by garbage collection. + /// The type of the object referenced. + // Token: 0x02000248 RID: 584 + [Serializable] + public sealed class WeakReference : ISerializable where T : class + { + /// Initializes a new instance of the class that references the specified object. + /// The object to reference, or . + // Token: 0x06001B8A RID: 7050 RVA: 0x00068700 File Offset: 0x00066900 + public WeakReference(T target) : this(target, false) + { + } + + /// Initializes a new instance of the class that references the specified object and uses the specified resurrection tracking. + /// The object to reference, or . + /// + /// to track the object after finalization; to track the object only until finalization. + // Token: 0x06001B8B RID: 7051 RVA: 0x0006870C File Offset: 0x0006690C + public WeakReference(T target, bool trackResurrection) + { + this.trackResurrection = trackResurrection; + GCHandleType type = trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak; + handle = GCHandle.Alloc(target, type); + } + + // Token: 0x06001B8C RID: 7052 RVA: 0x00068740 File Offset: 0x00066940 + private WeakReference(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + trackResurrection = info.GetBoolean("TrackResurrection"); + object value = info.GetValue("TrackedObject", typeof(T)); + GCHandleType type = trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak; + handle = GCHandle.Alloc(value, type); + } + + /// Populates a object with all the data necessary to serialize the current object. + /// An object that holds all the data necessary to serialize or deserialize the current object. + /// The location where serialized data is stored and retrieved. + /// + /// is . + // Token: 0x06001B8D RID: 7053 RVA: 0x000687A4 File Offset: 0x000669A4 + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + info.AddValue("TrackResurrection", trackResurrection); + if (handle.IsAllocated) + { + info.AddValue("TrackedObject", handle.Target); + return; + } + info.AddValue("TrackedObject", null); + } + + /// Sets the target object that is referenced by this object. + /// The new target object. + // Token: 0x06001B8E RID: 7054 RVA: 0x00068800 File Offset: 0x00066A00 + public void SetTarget(T target) + { + handle.Target = target; + } + + /// Tries to retrieve the target object that is referenced by the current object. + /// When this method returns, contains the target object, if it is available. This parameter is treated as uninitialized. + /// + /// if the target was retrieved; otherwise, . + // Token: 0x06001B8F RID: 7055 RVA: 0x00068813 File Offset: 0x00066A13 + public bool TryGetTarget(out T target) + { + if (!handle.IsAllocated) + { + target = default; + return false; + } + target = (T)handle.Target; + return target != null; + } + + /// Discards the reference to the target that is represented by the current object. + // Token: 0x06001B90 RID: 7056 RVA: 0x00068850 File Offset: 0x00066A50 + ~WeakReference() + { + handle.Free(); + } + + // Token: 0x04000F59 RID: 3929 + private GCHandle handle; + + // Token: 0x04000F5A RID: 3930 + private bool trackResurrection; + } +}