Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weak tracking messenger #28

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -26,11 +26,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 @@ -79,7 +79,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
4 changes: 2 additions & 2 deletions Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recip
/// 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="MessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
/// 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="MessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
/// <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 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ internal interface IDictionarySlim<in TKey> : IDictionarySlim
/// 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);
/// <returns>Whether or not the key was present.</returns>
bool TryRemove(TKey key);
}
}
79 changes: 79 additions & 0 deletions Microsoft.Toolkit.Mvvm/Messaging/Internals/Type2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Runtime.CompilerServices;

namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// A simple type representing an immutable pair of types.
/// </summary>
/// <remarks>
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
/// <see cref="TMessage"/> and <see cref="TToken"/> fields provide additional clarity reading
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
/// </remarks>
internal readonly struct Type2 : IEquatable<Type2>
{
/// <summary>
/// The type of registered message.
/// </summary>
public readonly Type TMessage;

/// <summary>
/// The type of registration token.
/// </summary>
public readonly Type TToken;

/// <summary>
/// Initializes a new instance of the <see cref="Type2"/> struct.
/// </summary>
/// <param name="tMessage">The type of registered message.</param>
/// <param name="tToken">The type of registration token.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type2(Type tMessage, Type tToken)
{
TMessage = tMessage;
TToken = tToken;
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Type2 other)
{
// We can't just use reference equality, as that's technically not guaranteed
// to work and might fail in very rare cases (eg. with type forwarding between
// different assemblies). Instead, we can use the == operator to compare for
// equality, which still avoids the callvirt overhead of calling Type.Equals,
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
return
TMessage == other.TMessage &&
TToken == other.TToken;
}

/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Type2 other && Equals(other);
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
unchecked
{
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
// This is not a problem in this case since we already know that the base
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
int hash = RuntimeHelpers.GetHashCode(TMessage);

hash = (hash << 5) + hash;

hash += RuntimeHelpers.GetHashCode(TToken);

return hash;
}
}
}
}
31 changes: 31 additions & 0 deletions Microsoft.Toolkit.Mvvm/Messaging/Internals/Unit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Runtime.CompilerServices;

namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
{
/// <summary>
/// An empty type representing a generic token with no specific value.
/// </summary>
internal readonly struct Unit : IEquatable<Unit>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Unit other)
{
return true;
}

/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is Unit;
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return 0;
}
}
}
41 changes: 0 additions & 41 deletions Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.Unit.cs

This file was deleted.

Loading