// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #if NETSTANDARD2_0 using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace System.Runtime.CompilerServices; /// /// A wrapper for /// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list. /// /// Tke key of items to store in the table. /// The values to store in the table. internal sealed class ConditionalWeakTable2 where TKey : class where TValue : class? { /// /// The underlying instance. /// private readonly ConditionalWeakTable table = new(); /// /// A supporting linked list to store keys in . This is needed to expose /// the ability to enumerate existing keys when there is no support for that in the BCL. /// private readonly LinkedList> keys = new(); /// public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) { return this.table.TryGetValue(key, out value); } /// public bool TryAdd(TKey key, TValue value) { if (!this.table.TryAdd(key, value)) { return false; } // Check if the list of keys contains the given key. // If it does, we can just stop here and return the result. foreach (WeakReference node in this.keys) { if (node.TryGetTarget(out TKey? target) && ReferenceEquals(target, key)) { return true; } } // Add the key to the list of weak references to track it _ = this.keys.AddFirst(new WeakReference(key)); return true; } /// public TValue GetValue(TKey key, ConditionalWeakTable.CreateValueCallback createValueCallback) { // Get or create the value. When this method returns, the key will be present in the table TValue value = this.table.GetValue(key, createValueCallback); // Check if the list of keys contains the given key. // If it does, we can just stop here and return the result. foreach (WeakReference node in this.keys) { if (node.TryGetTarget(out TKey? target) && ReferenceEquals(target, key)) { return value; } } // Add the key to the list of weak references to track it _ = this.keys.AddFirst(new WeakReference(key)); return value; } /// public bool Remove(TKey key) { return this.table.Remove(key); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() => new(this); /// /// A custom enumerator that traverses items in a instance. /// public ref struct Enumerator { /// /// The owner instance for the enumerator. /// private readonly ConditionalWeakTable2 owner; /// /// The current , if any. /// private LinkedListNode>? node; /// /// The current key, if available. /// private TKey? key; /// /// The current value, if available. /// private TValue? value; /// /// Indicates whether or not has been called at least once. /// private bool isFirstMoveNextPending; /// /// Initializes a new instance of the struct. /// /// The owner instance for the enumerator. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator(ConditionalWeakTable2 owner) { this.owner = owner; this.node = null; this.key = null; this.value = null; this.isFirstMoveNextPending = true; } /// public void Dispose() { } /// public bool MoveNext() { LinkedListNode>? node; if (!isFirstMoveNextPending) { node = this.node!.Next; } else { node = this.owner.keys.First; this.isFirstMoveNextPending = false; } while (node is not null) { LinkedListNode>? nextNode = node.Next; // Get the key and value for the current node if (node.Value.TryGetTarget(out TKey? target) && this.owner.table.TryGetValue(target!, out TValue? value)) { this.node = node; this.key = target; this.value = value; return true; } else { // If the current key has been collected, trim the list this.owner.keys.Remove(node); } node = nextNode; } return false; } /// /// Gets the current key. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TKey GetKey() { return this.key!; } /// /// Gets the current value. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue GetValue() { return this.value!; } } } #endif