Skip to content

Commit

Permalink
Merge pull request #28 from Sergio0694/feature/weak-tracking-messenger
Browse files Browse the repository at this point in the history
Weak tracking messenger
  • Loading branch information
Sergio0694 authored Sep 15, 2020
2 parents 6f0f7c3 + d8854e0 commit 554e771
Show file tree
Hide file tree
Showing 20 changed files with 1,128 additions and 252 deletions.
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

0 comments on commit 554e771

Please sign in to comment.