1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+ // See the LICENSE file in the project root for more information.
4+
5+ #if NETSTANDARD2_0
6+
7+ using System ;
8+ using System . Collections . Generic ;
9+ using System . Diagnostics . Contracts ;
10+ using System . Runtime . CompilerServices ;
11+
12+ namespace Microsoft . Toolkit . Mvvm . Messaging . Internals
13+ {
14+ /// <summary>
15+ /// A wrapper for <see cref="ConditionalWeakTable{TKey,TValue}"/>
16+ /// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
17+ /// </summary>
18+ /// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
19+ /// <typeparam name="TValue">The values to store in the table.</typeparam>
20+ internal sealed class ConditionalWeakTable2 < TKey , TValue >
21+ where TKey : class
22+ where TValue : class ?
23+ {
24+ /// <summary>
25+ /// The underlying <see cref="ConditionalWeakTable{TKey,TValue}"/> instance.
26+ /// </summary>
27+ private readonly ConditionalWeakTable < TKey , TValue > table = new ( ) ;
28+
29+ /// <summary>
30+ /// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
31+ /// the ability to enumerate existing keys when there is no support for that in the BCL.
32+ /// </summary>
33+ private readonly LinkedList < WeakReference < TKey > > keys = new ( ) ;
34+
35+ /// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
36+ public bool TryGetValue ( TKey key , out TValue ? value )
37+ {
38+ return this . table . TryGetValue ( key , out value ) ;
39+ }
40+
41+ /// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.GetValue"/>
42+ public TValue GetValue ( TKey key , ConditionalWeakTable < TKey , TValue > . CreateValueCallback createValueCallback )
43+ {
44+ // Get or create the value. When this method returns, the key will be present in the table
45+ TValue value = this . table . GetValue ( key , createValueCallback ) ;
46+
47+ // Check if the list of keys contains the given key.
48+ // If it does, we can just stop here and return the result.
49+ foreach ( WeakReference < TKey > node in this . keys )
50+ {
51+ if ( node . TryGetTarget ( out TKey ? target ) &&
52+ ReferenceEquals ( target , key ) )
53+ {
54+ return value ;
55+ }
56+ }
57+
58+ // Add the key to the list of weak references to track it
59+ this . keys . AddFirst ( new WeakReference < TKey > ( key ) ) ;
60+
61+ return value ;
62+ }
63+
64+ /// <inheritdoc cref="ConditionalWeakTable{TKey,TValue}.Remove"/>
65+ public bool Remove ( TKey key )
66+ {
67+ return this . table . Remove ( key ) ;
68+ }
69+
70+ /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
71+ [ Pure ]
72+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
73+ public Enumerator GetEnumerator ( ) => new ( this ) ;
74+
75+ /// <summary>
76+ /// A custom enumerator that traverses items in a <see cref="ConditionalWeakTable{TKey, TValue}"/> instance.
77+ /// </summary>
78+ public ref struct Enumerator
79+ {
80+ /// <summary>
81+ /// The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.
82+ /// </summary>
83+ private readonly ConditionalWeakTable2 < TKey , TValue > owner ;
84+
85+ /// <summary>
86+ /// The current <see cref="LinkedListNode{T}"/>, if any.
87+ /// </summary>
88+ private LinkedListNode < WeakReference < TKey > > ? node ;
89+
90+ /// <summary>
91+ /// The current <see cref="KeyValuePair{TKey, TValue}"/> to return.
92+ /// </summary>
93+ private KeyValuePair < TKey , TValue > current ;
94+
95+ /// <summary>
96+ /// Indicates whether or not <see cref="MoveNext"/> has been called at least once.
97+ /// </summary>
98+ private bool isFirstMoveNextPending ;
99+
100+ /// <summary>
101+ /// Initializes a new instance of the <see cref="Enumerator"/> struct.
102+ /// </summary>
103+ /// <param name="owner">The owner <see cref="ConditionalWeakTable2{TKey, TValue}"/> instance for the enumerator.</param>
104+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
105+ public Enumerator ( ConditionalWeakTable2 < TKey , TValue > owner )
106+ {
107+ this . owner = owner ;
108+ this . node = null ;
109+ this . current = default ;
110+ this . isFirstMoveNextPending = true ;
111+ }
112+
113+ /// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
114+ public bool MoveNext ( )
115+ {
116+ LinkedListNode < WeakReference < TKey > > ? node ;
117+
118+ if ( ! isFirstMoveNextPending )
119+ {
120+ node = this . node ! . Next ;
121+ }
122+ else
123+ {
124+ node = this . owner . keys . First ;
125+
126+ this . isFirstMoveNextPending = false ;
127+ }
128+
129+ while ( node is not null )
130+ {
131+ // Get the key and value for the current node
132+ if ( node . Value . TryGetTarget ( out TKey ? target ) &&
133+ this . owner . table . TryGetValue ( target ! , out TValue ? value ) )
134+ {
135+ this . node = node ;
136+ this . current = new KeyValuePair < TKey , TValue > ( target , value ) ;
137+
138+ return true ;
139+ }
140+ else
141+ {
142+ // If the current key has been collected, trim the list
143+ this . owner . keys . Remove ( node ) ;
144+ }
145+
146+ node = node . Next ;
147+ }
148+
149+ return false ;
150+ }
151+
152+ /// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
153+ public readonly KeyValuePair < TKey , TValue > Current
154+ {
155+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
156+ get => this . current ;
157+ }
158+ }
159+ }
160+ }
161+
162+ #endif
0 commit comments