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