22// The .NET Foundation licenses this file to you under the MIT license.
33// See the LICENSE file in the project root for more information.
44
5- #if NETSTANDARD2_1
6-
75using System ;
86using System . Buffers ;
97using System . Collections . Generic ;
108using System . Runtime . CompilerServices ;
119using Microsoft . Collections . Extensions ;
1210using Microsoft . Toolkit . Mvvm . Messaging . Internals ;
11+ #if NETSTANDARD2_1
12+ using RecipientsTable = System . Runtime . CompilerServices . ConditionalWeakTable < object , Microsoft . Collections . Extensions . IDictionarySlim > ;
13+ #else
14+ using RecipientsTable = Microsoft . Toolkit . Mvvm . Messaging . WeakRefMessenger . ConditionalWeakTable < object , Microsoft . Collections . Extensions . IDictionarySlim > ;
15+ #endif
1316
1417namespace Microsoft . Toolkit . Mvvm . Messaging
1518{
@@ -42,8 +45,7 @@ public sealed class WeakRefMessenger : IMessenger
4245 /// <summary>
4346 /// The map of currently registered recipients for all message types.
4447 /// </summary>
45- private readonly DictionarySlim < Type2 , ConditionalWeakTable < object , IDictionarySlim > > recipientsMap
46- = new DictionarySlim < Type2 , ConditionalWeakTable < object , IDictionarySlim > > ( ) ;
48+ private readonly DictionarySlim < Type2 , RecipientsTable > recipientsMap = new DictionarySlim < Type2 , RecipientsTable > ( ) ;
4749
4850 /// <summary>
4951 /// Gets the default <see cref="WeakRefMessenger"/> instance.
@@ -62,8 +64,8 @@ public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
6264 // Get the conditional table associated with the target recipient, for the current pair
6365 // of token and message types. If it exists, check if there is a matching token.
6466 return
65- this . recipientsMap . TryGetValue ( type2 , out ConditionalWeakTable < object , IDictionarySlim > ? table ) &&
66- table ! . TryGetValue ( recipient , out IDictionarySlim mapping ) &&
67+ this . recipientsMap . TryGetValue ( type2 , out RecipientsTable ? table ) &&
68+ table ! . TryGetValue ( recipient , out IDictionarySlim ? mapping ) &&
6769 Unsafe . As < DictionarySlim < TToken , object > > ( mapping ) . ContainsKey ( token ) ;
6870 }
6971 }
@@ -79,9 +81,9 @@ public void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken
7981 Type2 type2 = new Type2 ( typeof ( TMessage ) , typeof ( TToken ) ) ;
8082
8183 // Get the conditional table for the pair of type arguments, or create it if it doesn't exist
82- ref ConditionalWeakTable < object , IDictionarySlim > ? mapping = ref this . recipientsMap . GetOrAddValueRef ( type2 ) ;
84+ ref RecipientsTable ? mapping = ref this . recipientsMap . GetOrAddValueRef ( type2 ) ;
8385
84- mapping ??= new ConditionalWeakTable < object , IDictionarySlim > ( ) ;
86+ mapping ??= new RecipientsTable ( ) ;
8587
8688 // Get or create the handlers dictionary for the target recipient
8789 var map = Unsafe . As < DictionarySlim < TToken , object > > ( mapping . GetValue ( recipient , _ => new DictionarySlim < TToken , object > ( ) ) ) ;
@@ -174,7 +176,7 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
174176 Type2 type2 = new Type2 ( typeof ( TMessage ) , typeof ( TToken ) ) ;
175177
176178 // Try to get the target table
177- if ( ! this . recipientsMap . TryGetValue ( type2 , out ConditionalWeakTable < object , IDictionarySlim > ? table ) )
179+ if ( ! this . recipientsMap . TryGetValue ( type2 , out RecipientsTable ? table ) )
178180 {
179181 return message ;
180182 }
@@ -288,6 +290,87 @@ public void Reset()
288290 }
289291 }
290292
293+ #if ! NETSTANDARD2_1
294+ /// <summary>
295+ /// A wrapper for <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/>
296+ /// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
297+ /// </summary>
298+ /// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
299+ /// <typeparam name="TValue">The values to store in the table.</typeparam>
300+ internal sealed class ConditionalWeakTable < TKey , TValue >
301+ where TKey : class
302+ where TValue : class ?
303+ {
304+ /// <summary>
305+ /// The underlying <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/> instance.
306+ /// </summary>
307+ private readonly System . Runtime . CompilerServices . ConditionalWeakTable < TKey , TValue > table ;
308+
309+ /// <summary>
310+ /// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
311+ /// the ability to enumerate existing keys when there is no support for that in the BCL.
312+ /// </summary>
313+ private readonly LinkedList < WeakReference < TKey > > keys ;
314+
315+ /// <summary>
316+ /// Initializes a new instance of the <see cref="ConditionalWeakTable{TKey, TValue}"/> class.
317+ /// </summary>
318+ public ConditionalWeakTable ( )
319+ {
320+ this . table = new System . Runtime . CompilerServices . ConditionalWeakTable < TKey , TValue > ( ) ;
321+ this . keys = new LinkedList < WeakReference < TKey > > ( ) ;
322+ }
323+
324+ /// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
325+ public bool TryGetValue ( TKey key , out TValue value )
326+ {
327+ return this . table . TryGetValue ( key , out value ) ;
328+ }
329+
330+ /// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.GetValue"/>
331+ public TValue GetValue ( TKey key , System . Runtime . CompilerServices . ConditionalWeakTable < TKey , TValue > . CreateValueCallback createValueCallback )
332+ {
333+ // Get or create the value. When this method returns, the key will be present in the table
334+ TValue value = this . table . GetValue ( key , createValueCallback ) ;
335+
336+ // Check if the list of keys contains the given key.
337+ // If it does, we can just stop here and return the result.
338+ foreach ( WeakReference < TKey > node in this . keys )
339+ {
340+ if ( node . TryGetTarget ( out TKey ? target ) &&
341+ ReferenceEquals ( target , key ) )
342+ {
343+ return value ;
344+ }
345+ }
346+
347+ // Add the key to the list of weak references to track it
348+ this . keys . AddFirst ( new WeakReference < TKey > ( key ) ) ;
349+
350+ return value ;
351+ }
352+
353+ /// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.Remove"/>
354+ public bool Remove ( TKey key )
355+ {
356+ return this . table . Remove ( key ) ;
357+ }
358+
359+ /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
360+ public IEnumerator < KeyValuePair < TKey , TValue > > GetEnumerator ( )
361+ {
362+ foreach ( WeakReference < TKey > node in this . keys )
363+ {
364+ if ( node . TryGetTarget ( out TKey ? target ) &&
365+ this . table . TryGetValue ( target , out TValue value ) )
366+ {
367+ yield return new KeyValuePair < TKey , TValue > ( target , value ) ;
368+ }
369+ }
370+ }
371+ }
372+ #endif
373+
291374 /// <summary>
292375 /// A simple buffer writer implementation using pooled arrays.
293376 /// </summary>
@@ -395,5 +478,3 @@ private static void ThrowInvalidOperationExceptionForDuplicateRegistration()
395478 }
396479 }
397480}
398-
399- #endif
0 commit comments