Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c54bf01
Enabled shared object[] pool for Messenger
Sergio0694 Aug 12, 2020
72b6520
Minor performance improvement
Sergio0694 Aug 12, 2020
fd887c6
Merge branch 'master' into optimization/messenger-shared-pool
Sergio0694 Aug 12, 2020
6ecdc05
Fixed a typo and tweaked a comment
Sergio0694 Aug 12, 2020
5066dff
Added MessageHandler<TMessage> delegate, removed closures
Sergio0694 Aug 17, 2020
f886f28
Fixed some XML comments
Sergio0694 Aug 17, 2020
c45bb99
Updated unit tests
Sergio0694 Aug 17, 2020
eef4d0a
Fixed an index exception in Messenger.Send
Sergio0694 Aug 17, 2020
dea8b89
Tweaked XML docs and comments
Sergio0694 Aug 17, 2020
02790ab
Added IMessenger.Cleanup API (to help with extensibility)
Sergio0694 Aug 17, 2020
332cda8
Merge branch 'master' into optimization/messenger-shared-pool
Sergio0694 Aug 21, 2020
e514869
Added missing contravariant type argument modifier
Sergio0694 Aug 25, 2020
d86f794
Added MessageHandler<in TRecipient> "covariance"
Sergio0694 Sep 5, 2020
af91276
Tweaked IMessenger and Messenger XML docs
Sergio0694 Sep 6, 2020
3293fa6
Minor code refactoring
Sergio0694 Sep 7, 2020
93456ae
Added tests for correct recipients
Sergio0694 Sep 10, 2020
b0f2edd
Fixed accidental O(logn) to O(n) lookup slowdown
Sergio0694 Sep 12, 2020
89efbea
Small code refactoring and improvements
Sergio0694 Sep 12, 2020
cf515e6
Added WeakRefMessenger type
Sergio0694 Sep 12, 2020
5840ad0
Added support for multiple messenger types to tests
Sergio0694 Sep 12, 2020
d68f9d2
Enabled tests for WeakRefMessenger
Sergio0694 Sep 12, 2020
78bbd6e
Removed leftover code no longer needed
Sergio0694 Sep 12, 2020
9809522
Code refactoring, removed duplicate types
Sergio0694 Sep 12, 2020
c6553d1
Removed some unnecessary bounds checks
Sergio0694 Sep 13, 2020
6f0f7c3
Minor code refactoring
Sergio0694 Sep 13, 2020
70d1986
Merge branch 'optimization/messenger-shared-pool' into feature/weak-t…
Sergio0694 Sep 13, 2020
9d8cff6
Fixed a merge error, minor code refactoring
Sergio0694 Sep 13, 2020
15a0c3b
Minor code tweaks
Sergio0694 Sep 13, 2020
1cf168b
Backported weak messenger to .NET Standard 2.0
Sergio0694 Sep 13, 2020
9e7f12a
Enabled weak messenger tests on .NET Standard 2.0
Sergio0694 Sep 13, 2020
8212a95
Improved cleanup on .NET Standard 2.0 polyfill
Sergio0694 Sep 13, 2020
0e9e7db
Removed explicit interface implementation
Sergio0694 Sep 13, 2020
72a2236
Minor code refactoring
Sergio0694 Sep 14, 2020
f583a66
Added auto trimming to CVT<,> polyfill
Sergio0694 Sep 14, 2020
8c17b0a
Added recipient garbage collect test
Sergio0694 Sep 14, 2020
603e9d8
Removed unnecessary property
Sergio0694 Sep 14, 2020
2613c56
Improved test coverage
Sergio0694 Sep 14, 2020
d8854e0
Renamed messenger types
Sergio0694 Sep 15, 2020
554e771
Merge pull request #28 from Sergio0694/feature/weak-tracking-messenger
Sergio0694 Sep 15, 2020
dcec42b
Merge branch 'master' into optimization/messenger-shared-pool
Sergio0694 Sep 15, 2020
2a3cf84
Added missing file headers
Sergio0694 Sep 15, 2020
ff64983
Fixed unit test (changed type after refactoring)
Sergio0694 Sep 15, 2020
32656db
Minor memory usage improvement
Sergio0694 Sep 20, 2020
e8dbe3c
Merge remote-tracking branch 'upstream/master' into optimization/mess…
Sergio0694 Sep 24, 2020
72d4319
Merge branch 'master' into optimization/messenger-shared-pool
Sergio0694 Sep 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public abstract class ObservableRecipient : ObservableObject
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
/// </summary>
/// <remarks>
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
/// This constructor will produce an instance that will use the <see cref="WeakReferenceMessenger.Default"/> instance
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
/// </remarks>
protected ObservableRecipient()
: this(Messaging.Messenger.Default)
: this(WeakReferenceMessenger.Default)
{
}

Expand Down Expand Up @@ -78,7 +78,7 @@ public bool IsActive
/// <remarks>
/// The base implementation registers all messages for this recipients that have been declared
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
/// For more details on how this works, see the <see cref="IMessengerExtensions.RegisterAll"/> method.
/// If you need more fine tuned control, want to register messages individually or just prefer
/// the lambda-style syntax for message registration, override this method and register manually.
/// </remarks>
Expand Down
76 changes: 74 additions & 2 deletions Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,70 @@

namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// A <see langword="delegate"/> used to represent actions to invoke when a message is received.
/// The recipient is given as an input argument to allow message registrations to avoid creating
/// closures: if an instance method on a recipient needs to be invoked it is possible to just
/// cast the recipient to the right type and then access the local method from that instance.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="recipient">The recipient that is receiving the message.</param>
/// <param name="message">The message being received.</param>
public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recipient, TMessage message)
where TRecipient : class
where TMessage : class;

/// <summary>
/// An interface for a type providing the ability to exchange messages between different objects.
/// This can be useful to decouple different modules of an application without having to keep strong
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
/// identified by a token, and to have different messengers in different sections of an applications.
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
/// <code>
/// public sealed class LoginCompletedMessage { }
/// </code>
/// Then, register your a recipient for this message:
/// <code>
/// Messenger.Default.Register&lt;MyRecipientType, LoginCompletedMessage&gt;(this, (r, m) =>
/// {
/// // Handle the message here...
/// });
/// </code>
/// The message handler here is a lambda expression taking two parameters: the recipient and the message.
/// This is done to avoid the allocations for the closures that would've been generated if the expression
/// had captured the current instance. The recipient type parameter is used so that the recipient can be
/// directly accessed within the handler without the need to manually perform type casts. This allows the
/// code to be less verbose and more reliable, as all the checks are done just at build time. If the handler
/// is defined within the same type as the recipient, it is also possible to directly access private members.
/// This allows the message handler to be a static method, which enables the C# compiler to perform a number
/// of additional memory optimizations (such as caching the delegate, avoiding unnecessary memory allocations).
/// Finally, send a message when needed, like so:
/// <code>
/// Messenger.Default.Send&lt;LoginCompletedMessage&gt;();
/// </code>
/// Additionally, the method group syntax can also be used to specify the message handler
/// to invoke when receiving a message, if a method with the right signature is available
/// in the current scope. This is helpful to keep the registration and handling logic separate.
/// Following up from the previous example, consider a class having this method:
/// <code>
/// private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
/// {
/// // Handle the message there
/// }
/// </code>
/// The registration can then be performed in a single line like so:
/// <code>
/// Messenger.Default.Register(this, Receive);
/// </code>
/// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient,TMessage}"/> instance
/// compatible with <see cref="IMessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
/// This will also work if multiple overloads of that method are available, each handling a different
/// message type: the C# compiler will automatically pick the right one for the current message type.
/// It is also possible to register message handlers explicitly using the <see cref="IRecipient{TMessage}"/> interface.
/// To do so, the recipient just needs to implement the interface and then call the
/// <see cref="IMessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
/// all the handlers that are declared by the recipient type. Registration for individual handlers is supported as well.
/// </summary>
public interface IMessenger
{
Expand All @@ -28,13 +90,15 @@ bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="token">A token used to determine the receiving channel to use.</param>
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
where TToken : IEquatable<TToken>;

Expand Down Expand Up @@ -83,6 +147,14 @@ TMessage Send<TMessage, TToken>(TMessage message, TToken token)
where TMessage : class
where TToken : IEquatable<TToken>;

/// <summary>
/// Performs a cleanup on the current messenger.
/// Invoking this method does not unregister any of the currently registered
/// recipient, and it can be used to perform cleanup operations such as
/// trimming the internal data structures of a messenger implementation.
/// </summary>
void Cleanup();

/// <summary>
/// Resets the <see cref="IMessenger"/> instance and unregisters all the existing recipients.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Mvvm.Messaging.Internals;

namespace Microsoft.Toolkit.Mvvm.Messaging
{
/// <summary>
/// Extensions for the <see cref="IMessenger"/> type.
/// </summary>
public static partial class MessengerExtensions
public static class IMessengerExtensions
{
/// <summary>
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
/// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
/// the <see cref="MessengerExtensions"/> type is referenced, even if that is done just to use methods
/// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
/// </summary>
Expand All @@ -32,7 +33,7 @@ private static class MethodInfos
static MethodInfos()
{
RegisterIRecipient = (
from methodInfo in typeof(MessengerExtensions).GetMethods()
from methodInfo in typeof(IMessengerExtensions).GetMethods()
where methodInfo.Name == nameof(Register) &&
methodInfo.IsGenericMethod &&
methodInfo.GetGenericArguments().Length == 2
Expand Down Expand Up @@ -174,7 +175,7 @@ static Action<IMessenger, object, TToken> GetRegistrationAction(Type type, Metho
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
where TMessage : class
{
messenger.Register<TMessage, Unit>(recipient, default, recipient.Receive);
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
}

/// <summary>
Expand All @@ -191,7 +192,7 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register<TMessage, TToken>(recipient, token, recipient.Receive);
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
}

/// <summary>
Expand All @@ -200,13 +201,47 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
public static void Register<TMessage>(this IMessenger messenger, object recipient, Action<TMessage> action)
public static void Register<TMessage>(this IMessenger messenger, object recipient, MessageHandler<object, TMessage> handler)
where TMessage : class
{
messenger.Register(recipient, default(Unit), action);
messenger.Register(recipient, default(Unit), handler);
}

/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
public static void Register<TRecipient, TMessage>(this IMessenger messenger, TRecipient recipient, MessageHandler<TRecipient, TMessage> handler)
where TRecipient : class
where TMessage : class
{
messenger.Register(recipient, default(Unit), handler);
}

/// <summary>
/// Registers a recipient for a given type of message.
/// </summary>
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <param name="token">A token used to determine the receiving channel to use.</param>
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
public static void Register<TMessage, TToken>(this IMessenger messenger, object recipient, TToken token, MessageHandler<object, TMessage> handler)
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register(recipient, token, handler);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ public void Clear()
this.entries = InitialEntries;
}

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

/// <inheritdoc/>
public bool TryRemove(TKey key, out object? result)
public bool TryRemove(TKey key)
{
return TryRemove(key, out _);
}

/// <summary>
/// Tries to remove a value with a specified key, if present.
/// </summary>
/// <param name="key">The key of the value to remove.</param>
/// <param name="result">The removed value, if it was present.</param>
/// <returns>Whether or not the key was present.</returns>
public bool TryRemove(TKey key, out TValue? result)
{
Entry[] entries = this.entries;
int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1);
Expand Down Expand Up @@ -218,13 +233,6 @@ public bool TryRemove(TKey key, out object? result)
return false;
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Remove(TKey key)
{
return TryRemove(key, out _);
}

/// <summary>
/// Gets the value for the specified key, or, if the key is not present,
/// adds an entry and returns the value by ref. This makes it possible to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,10 @@ internal interface IDictionarySlim<in TKey> : IDictionarySlim
where TKey : IEquatable<TKey>
{
/// <summary>
/// Tries to remove a value with a specified key.
/// Tries to remove a value with a specified key, if present.
/// </summary>
/// <param name="key">The key of the value to remove.</param>
/// <param name="result">The removed value, if it was present.</param>
/// <returns>.Whether or not the key was present.</returns>
bool TryRemove(TKey key, out object? result);

/// <summary>
/// Removes an item from the dictionary with the specified key, if present.
/// </summary>
/// <param name="key">The key of the item to remove.</param>
/// <returns>Whether or not an item was removed.</returns>
bool Remove(TKey key);
/// <returns>Whether or not the key was present.</returns>
bool TryRemove(TKey key);
}
}
Loading