Skip to content

Commit 554e771

Browse files
authored
Merge pull request #28 from Sergio0694/feature/weak-tracking-messenger
Weak tracking messenger
2 parents 6f0f7c3 + d8854e0 commit 554e771

20 files changed

+1128
-252
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public abstract class ObservableRecipient : ObservableObject
2626
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
2727
/// </summary>
2828
/// <remarks>
29-
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
29+
/// This constructor will produce an instance that will use the <see cref="WeakReferenceMessenger.Default"/> instance
3030
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
3131
/// </remarks>
3232
protected ObservableRecipient()
33-
: this(Messaging.Messenger.Default)
33+
: this(WeakReferenceMessenger.Default)
3434
{
3535
}
3636

@@ -79,7 +79,7 @@ public bool IsActive
7979
/// <remarks>
8080
/// The base implementation registers all messages for this recipients that have been declared
8181
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
82-
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
82+
/// For more details on how this works, see the <see cref="IMessengerExtensions.RegisterAll"/> method.
8383
/// If you need more fine tuned control, want to register messages individually or just prefer
8484
/// the lambda-style syntax for message registration, override this method and register manually.
8585
/// </remarks>

Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recip
6464
/// Messenger.Default.Register(this, Receive);
6565
/// </code>
6666
/// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient,TMessage}"/> instance
67-
/// compatible with <see cref="MessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
67+
/// compatible with <see cref="IMessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
6868
/// This will also work if multiple overloads of that method are available, each handling a different
6969
/// message type: the C# compiler will automatically pick the right one for the current message type.
7070
/// It is also possible to register message handlers explicitly using the <see cref="IRecipient{TMessage}"/> interface.
7171
/// To do so, the recipient just needs to implement the interface and then call the
72-
/// <see cref="MessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
72+
/// <see cref="IMessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
7373
/// all the handlers that are declared by the recipient type. Registration for individual handlers is supported as well.
7474
/// </summary>
7575
public interface IMessenger

Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88
using System.Linq.Expressions;
99
using System.Reflection;
1010
using System.Runtime.CompilerServices;
11+
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
1112

1213
namespace Microsoft.Toolkit.Mvvm.Messaging
1314
{
1415
/// <summary>
1516
/// Extensions for the <see cref="IMessenger"/> type.
1617
/// </summary>
17-
public static partial class MessengerExtensions
18+
public static class IMessengerExtensions
1819
{
1920
/// <summary>
2021
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
2122
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
2223
/// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
23-
/// the <see cref="MessengerExtensions"/> type is referenced, even if that is done just to use methods
24+
/// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
2425
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
2526
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
2627
/// </summary>
@@ -32,7 +33,7 @@ private static class MethodInfos
3233
static MethodInfos()
3334
{
3435
RegisterIRecipient = (
35-
from methodInfo in typeof(MessengerExtensions).GetMethods()
36+
from methodInfo in typeof(IMessengerExtensions).GetMethods()
3637
where methodInfo.Name == nameof(Register) &&
3738
methodInfo.IsGenericMethod &&
3839
methodInfo.GetGenericArguments().Length == 2

Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/Internals/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,11 @@ public void Clear()
130130
this.entries = InitialEntries;
131131
}
132132

133-
/// <inheritdoc cref="Dictionary{TKey,TValue}.ContainsKey"/>
133+
/// <summary>
134+
/// Checks whether or not the dictionary contains a pair with a specified key.
135+
/// </summary>
136+
/// <param name="key">The key to look for.</param>
137+
/// <returns>Whether or not the key was present in the dictionary.</returns>
134138
public bool ContainsKey(TKey key)
135139
{
136140
Entry[] entries = this.entries;
@@ -176,7 +180,18 @@ public bool TryGetValue(TKey key, out TValue? value)
176180
}
177181

178182
/// <inheritdoc/>
179-
public bool TryRemove(TKey key, out object? result)
183+
public bool TryRemove(TKey key)
184+
{
185+
return TryRemove(key, out _);
186+
}
187+
188+
/// <summary>
189+
/// Tries to remove a value with a specified key, if present.
190+
/// </summary>
191+
/// <param name="key">The key of the value to remove.</param>
192+
/// <param name="result">The removed value, if it was present.</param>
193+
/// <returns>Whether or not the key was present.</returns>
194+
public bool TryRemove(TKey key, out TValue? result)
180195
{
181196
Entry[] entries = this.entries;
182197
int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1);

Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/Internals/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ internal interface IDictionarySlim<in TKey> : IDictionarySlim
1717
/// Tries to remove a value with a specified key, if present.
1818
/// </summary>
1919
/// <param name="key">The key of the value to remove.</param>
20-
/// <param name="result">The removed value, if it was present.</param>
21-
/// <returns>.Whether or not the key was present.</returns>
22-
bool TryRemove(TKey key, out object? result);
20+
/// <returns>Whether or not the key was present.</returns>
21+
bool TryRemove(TKey key);
2322
}
2423
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
5+
{
6+
/// <summary>
7+
/// A simple type representing an immutable pair of types.
8+
/// </summary>
9+
/// <remarks>
10+
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
11+
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
12+
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
13+
/// <see cref="TMessage"/> and <see cref="TToken"/> fields provide additional clarity reading
14+
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
15+
/// </remarks>
16+
internal readonly struct Type2 : IEquatable<Type2>
17+
{
18+
/// <summary>
19+
/// The type of registered message.
20+
/// </summary>
21+
public readonly Type TMessage;
22+
23+
/// <summary>
24+
/// The type of registration token.
25+
/// </summary>
26+
public readonly Type TToken;
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="Type2"/> struct.
30+
/// </summary>
31+
/// <param name="tMessage">The type of registered message.</param>
32+
/// <param name="tToken">The type of registration token.</param>
33+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34+
public Type2(Type tMessage, Type tToken)
35+
{
36+
TMessage = tMessage;
37+
TToken = tToken;
38+
}
39+
40+
/// <inheritdoc/>
41+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42+
public bool Equals(Type2 other)
43+
{
44+
// We can't just use reference equality, as that's technically not guaranteed
45+
// to work and might fail in very rare cases (eg. with type forwarding between
46+
// different assemblies). Instead, we can use the == operator to compare for
47+
// equality, which still avoids the callvirt overhead of calling Type.Equals,
48+
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
49+
return
50+
TMessage == other.TMessage &&
51+
TToken == other.TToken;
52+
}
53+
54+
/// <inheritdoc/>
55+
public override bool Equals(object? obj)
56+
{
57+
return obj is Type2 other && Equals(other);
58+
}
59+
60+
/// <inheritdoc/>
61+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
62+
public override int GetHashCode()
63+
{
64+
unchecked
65+
{
66+
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
67+
// This is not a problem in this case since we already know that the base
68+
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
69+
int hash = RuntimeHelpers.GetHashCode(TMessage);
70+
71+
hash = (hash << 5) + hash;
72+
73+
hash += RuntimeHelpers.GetHashCode(TToken);
74+
75+
return hash;
76+
}
77+
}
78+
}
79+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
5+
{
6+
/// <summary>
7+
/// An empty type representing a generic token with no specific value.
8+
/// </summary>
9+
internal readonly struct Unit : IEquatable<Unit>
10+
{
11+
/// <inheritdoc/>
12+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
13+
public bool Equals(Unit other)
14+
{
15+
return true;
16+
}
17+
18+
/// <inheritdoc/>
19+
public override bool Equals(object? obj)
20+
{
21+
return obj is Unit;
22+
}
23+
24+
/// <inheritdoc/>
25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
public override int GetHashCode()
27+
{
28+
return 0;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)