@@ -50,8 +50,8 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
50
50
/// <code>
51
51
/// Messenger.Default.Register<LoginCompletedMessage>(this, Receive);
52
52
/// </code>
53
- /// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TMessage}"/> instance
54
- /// compatible with the <see cref="MessengerExtensions.Register{T }(IMessenger,object ,MessageHandler{T })"/> method .
53
+ /// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient, TMessage}"/> instance
54
+ /// compatible with <see cref="MessengerExtensions.Register{TRecipient,TMessage }(IMessenger,TRecipient ,MessageHandler{TRecipient,TMessage })"/>.
55
55
/// This will also work if multiple overloads of that method are available, each handling a different
56
56
/// message type: the C# compiler will automatically pick the right one for the current message type.
57
57
/// For info on the other available features, check the <see cref="IMessenger"/> interface.
@@ -66,18 +66,23 @@ public sealed class Messenger : IMessenger
66
66
// | ________(recipients registrations)___________\________/ / __/
67
67
// | / _______(channel registrations)_____\___________________/ /
68
68
// | / / \ /
69
- // DictionarySlim<Recipient, DictionarySlim<TToken, MessageHandler<TMessage>>> mapping = Mapping<TMessage, TToken>
70
- // / / \ / /
71
- // ___(Type2.tToken)____/ / \______/___________________ /
69
+ // DictionarySlim<Recipient, DictionarySlim<TToken, MessageHandler<object, TMessage>>> mapping = Mapping<TMessage, TToken>
70
+ // / / \ / /
71
+ // ___(Type2.tToken)____/ / \______/_________________________ /
72
72
// /________________(Type2.tMessage)____/ /
73
73
// / ________________________________________/
74
74
// / /
75
75
// DictionarySlim<Type2, IMapping> typesMap;
76
76
// --------------------------------------------------------------------------------------------------------
77
- // Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds
78
- // the references from registered recipients to handlers. The handlers are stored in a <TToken, Action<TMessage>>
79
- // dictionary, so that each recipient can have up to one registered handler for a given token, for each
80
- // message type. Each mapping is stored in the types map, which associates each pair of concrete types to its
77
+ // Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds the references
78
+ // from registered recipients to handlers. The handlers are stored in a <TToken, MessageHandler<object, TMessage>>
79
+ // dictionary, so that each recipient can have up to one registered handler for a given token, for each message type.
80
+ // Note that the registered handlers are unsafe-cast to a MessageHandler<object, TMessage> instance even if they were
81
+ // actually of type MessageHandler<TRecipient, TMessage>. This allows the messenger to track and invoke type-specific
82
+ // handlers without using reflection and without having to capture the input handler in a proxy delegate, causing one
83
+ // extra memory allocations and adding overhead. The type conversion is guaranteed to be respected due to how the
84
+ // messenger type itself works - as registered handlers are always invoked on their respective recipients.
85
+ // Each mapping is stored in the types map, which associates each pair of concrete types to its
81
86
// mapping instance. Mapping instances are exposed as IMapping items, as each will be a closed type over
82
87
// a different combination of TMessage and TToken generic type parameters. Each existing recipient is also stored in
83
88
// the main recipients map, along with a set of all the existing dictionaries of handlers for that recipient (for all
@@ -137,7 +142,8 @@ public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
137
142
}
138
143
139
144
/// <inheritdoc/>
140
- public void Register < TMessage , TToken > ( object recipient , TToken token , MessageHandler < TMessage > handler )
145
+ public void Register < TRecipient , TMessage , TToken > ( TRecipient recipient , TToken token , MessageHandler < TRecipient , TMessage > handler )
146
+ where TRecipient : class
141
147
where TMessage : class
142
148
where TToken : IEquatable < TToken >
143
149
{
@@ -146,19 +152,20 @@ public void Register<TMessage, TToken>(object recipient, TToken token, MessageHa
146
152
// Get the <TMessage, TToken> registration list for this recipient
147
153
Mapping < TMessage , TToken > mapping = GetOrAddMapping < TMessage , TToken > ( ) ;
148
154
var key = new Recipient ( recipient ) ;
149
- ref DictionarySlim < TToken , MessageHandler < TMessage > > ? map = ref mapping . GetOrAddValueRef ( key ) ;
155
+ ref DictionarySlim < TToken , MessageHandler < object , TMessage > > ? map = ref mapping . GetOrAddValueRef ( key ) ;
150
156
151
- map ??= new DictionarySlim < TToken , MessageHandler < TMessage > > ( ) ;
157
+ map ??= new DictionarySlim < TToken , MessageHandler < object , TMessage > > ( ) ;
152
158
153
159
// Add the new registration entry
154
- ref MessageHandler < TMessage > ? registeredHandler = ref map . GetOrAddValueRef ( token ) ;
160
+ ref MessageHandler < object , TMessage > ? registeredHandler = ref map . GetOrAddValueRef ( token ) ;
155
161
156
162
if ( ! ( registeredHandler is null ) )
157
163
{
158
164
ThrowInvalidOperationExceptionForDuplicateRegistration ( ) ;
159
165
}
160
166
161
- registeredHandler = handler ;
167
+ // Treat the input delegate as if it was covariant (see comments below in the Send method)
168
+ registeredHandler = Unsafe . As < MessageHandler < object , TMessage > > ( handler ) ;
162
169
163
170
// Update the total counter for handlers for the current type parameters
164
171
mapping . TotalHandlersCount ++ ;
@@ -356,7 +363,7 @@ public void Unregister<TMessage, TToken>(object recipient, TToken token)
356
363
357
364
var key = new Recipient ( recipient ) ;
358
365
359
- if ( ! mapping ! . TryGetValue ( key , out DictionarySlim < TToken , MessageHandler < TMessage > > ? dictionary ) )
366
+ if ( ! mapping ! . TryGetValue ( key , out DictionarySlim < TToken , MessageHandler < object , TMessage > > ? dictionary ) )
360
367
{
361
368
return ;
362
369
}
@@ -461,9 +468,14 @@ public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
461
468
{
462
469
// We're doing an unsafe cast to skip the type checks again.
463
470
// See the comments in the UnregisterAll method for more info.
464
- MessageHandler < TMessage > handler = Unsafe . As < MessageHandler < TMessage > > ( Unsafe . Add ( ref handlersRef , ( IntPtr ) ( void * ) ( uint ) j ) ) ;
465
-
466
- handler ( Unsafe . Add ( ref recipientsRef , ( IntPtr ) ( void * ) ( uint ) j ) , message ) ;
471
+ object handler = Unsafe . Add ( ref handlersRef , ( IntPtr ) ( void * ) ( uint ) j ) ;
472
+ object recipient = Unsafe . Add ( ref recipientsRef , ( IntPtr ) ( void * ) ( uint ) j ) ;
473
+
474
+ // Here we perform an unsafe cast to enable covariance for delegate types.
475
+ // We know that the input recipient will always respect the type constraints
476
+ // of each original input delegate, and doing so allows us to still invoke
477
+ // them all from here without worrying about specific generic type arguments.
478
+ Unsafe . As < MessageHandler < object , TMessage > > ( handler ) ( recipient , message ) ;
467
479
}
468
480
}
469
481
finally
@@ -559,7 +571,7 @@ private Mapping<TMessage, TToken> GetOrAddMapping<TMessage, TToken>()
559
571
/// This type is defined for simplicity and as a workaround for the lack of support for using type aliases
560
572
/// over open generic types in C# (using type aliases can only be used for concrete, closed types).
561
573
/// </remarks>
562
- private sealed class Mapping < TMessage , TToken > : DictionarySlim < Recipient , DictionarySlim < TToken , MessageHandler < TMessage > > > , IMapping
574
+ private sealed class Mapping < TMessage , TToken > : DictionarySlim < Recipient , DictionarySlim < TToken , MessageHandler < object , TMessage > > > , IMapping
563
575
where TMessage : class
564
576
where TToken : IEquatable < TToken >
565
577
{
0 commit comments