Skip to content

Commit 1cf168b

Browse files
committed
Backported weak messenger to .NET Standard 2.0
1 parent 15a0c3b commit 1cf168b

File tree

1 file changed

+92
-11
lines changed

1 file changed

+92
-11
lines changed

Microsoft.Toolkit.Mvvm/Messaging/WeakRefMessenger.cs

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
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-
75
using System;
86
using System.Buffers;
97
using System.Collections.Generic;
108
using System.Runtime.CompilerServices;
119
using Microsoft.Collections.Extensions;
1210
using 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

1417
namespace 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

Comments
 (0)