// 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