IMessengerExtensions.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Linq;
  7. using System.Linq.Expressions;
  8. using System.Reflection;
  9. using System.Runtime.CompilerServices;
  10. using CommunityToolkit.Mvvm.Messaging.Internals;
  11. namespace CommunityToolkit.Mvvm.Messaging;
  12. /// <summary>
  13. /// Extensions for the <see cref="IMessenger"/> type.
  14. /// </summary>
  15. public static partial class IMessengerExtensions
  16. {
  17. /// <summary>
  18. /// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
  19. /// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
  20. /// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
  21. /// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
  22. /// that do not actually require this <see cref="MethodInfo"/> instance to be available.
  23. /// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
  24. /// </summary>
  25. private static class MethodInfos
  26. {
  27. /// <summary>
  28. /// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
  29. /// </summary>
  30. public static readonly MethodInfo RegisterIRecipient = new Action<IMessenger, IRecipient<object>, Unit>(Register).Method.GetGenericMethodDefinition();
  31. }
  32. /// <summary>
  33. /// A non-generic version of <see cref="DiscoveredRecipients{TToken}"/>.
  34. /// </summary>
  35. private static class DiscoveredRecipients
  36. {
  37. /// <summary>
  38. /// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track the preloaded registration action for each recipient.
  39. /// </summary>
  40. public static readonly ConditionalWeakTable<Type, Action<IMessenger, object>?> RegistrationMethods = new();
  41. }
  42. /// <summary>
  43. /// A class that acts as a static container to associate a <see cref="ConditionalWeakTable{TKey,TValue}"/> instance to each
  44. /// <typeparamref name="TToken"/> type in use. This is done because we can only use a single type as key, but we need to track
  45. /// associations of each recipient type also across different communication channels, each identified by a token.
  46. /// Since the token is actually a compile-time parameter, we can use a wrapping class to let the runtime handle a different
  47. /// instance for each generic type instantiation. This lets us only worry about the recipient type being inspected.
  48. /// </summary>
  49. /// <typeparam name="TToken">The token indicating what channel to use.</typeparam>
  50. private static class DiscoveredRecipients<TToken>
  51. where TToken : IEquatable<TToken>
  52. {
  53. /// <summary>
  54. /// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track the preloaded registration action for each recipient.
  55. /// </summary>
  56. public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>> RegistrationMethods = new();
  57. }
  58. /// <summary>
  59. /// Checks whether or not a given recipient has already been registered for a message.
  60. /// </summary>
  61. /// <typeparam name="TMessage">The type of message to check for the given recipient.</typeparam>
  62. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to check the registration.</param>
  63. /// <param name="recipient">The target recipient to check the registration for.</param>
  64. /// <returns>Whether or not <paramref name="recipient"/> has already been registered for the specified message.</returns>
  65. /// <remarks>This method will use the default channel to check for the requested registration.</remarks>
  66. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="recipient"/> are <see langword="null"/>.</exception>
  67. public static bool IsRegistered<TMessage>(this IMessenger messenger, object recipient)
  68. where TMessage : class
  69. {
  70. ArgumentNullException.ThrowIfNull(messenger);
  71. ArgumentNullException.ThrowIfNull(recipient);
  72. return messenger.IsRegistered<TMessage, Unit>(recipient, default);
  73. }
  74. /// <summary>
  75. /// Registers all declared message handlers for a given recipient, using the default channel.
  76. /// </summary>
  77. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  78. /// <param name="recipient">The recipient that will receive the messages.</param>
  79. /// <remarks>See notes for <see cref="RegisterAll{TToken}(IMessenger,object,TToken)"/> for more info.</remarks>
  80. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="recipient"/> are <see langword="null"/>.</exception>
  81. [RequiresUnreferencedCode(
  82. "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
  83. "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
  84. "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
  85. [RequiresDynamicCode(
  86. "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
  87. "If that is present, the method is AOT safe, as the only methods being invoked to register the messages will be the ones produced by the source generator. " +
  88. "If it isn't, this method will need to dynamically create the generic methods to register messages, which might not be available at runtime.")]
  89. public static void RegisterAll(this IMessenger messenger, object recipient)
  90. {
  91. ArgumentNullException.ThrowIfNull(messenger);
  92. ArgumentNullException.ThrowIfNull(recipient);
  93. // We use this method as a callback for the conditional weak table, which will handle
  94. // thread-safety for us. This first callback will try to find a generated method for the
  95. // target recipient type, and just invoke it to get the delegate to cache and use later.
  96. [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
  97. static Action<IMessenger, object>? LoadRegistrationMethodsForType(Type recipientType)
  98. {
  99. if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType &&
  100. extensionsType.GetMethod("CreateAllMessagesRegistrator", new[] { recipientType }) is MethodInfo methodInfo)
  101. {
  102. return (Action<IMessenger, object>)methodInfo.Invoke(null, new object?[] { null })!;
  103. }
  104. return null;
  105. }
  106. // Try to get the cached delegate, if the generator has run correctly
  107. Action<IMessenger, object>? registrationAction = DiscoveredRecipients.RegistrationMethods.GetValue(
  108. recipient.GetType(),
  109. LoadRegistrationMethodsForType);
  110. if (registrationAction is not null)
  111. {
  112. registrationAction(messenger, recipient);
  113. }
  114. else
  115. {
  116. messenger.RegisterAll(recipient, default(Unit));
  117. }
  118. }
  119. /// <summary>
  120. /// Registers all declared message handlers for a given recipient.
  121. /// </summary>
  122. /// <typeparam name="TToken">The type of token to identify what channel to use to receive messages.</typeparam>
  123. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  124. /// <param name="recipient">The recipient that will receive the messages.</param>
  125. /// <param name="token">The token indicating what channel to use.</param>
  126. /// <remarks>
  127. /// This method will register all messages corresponding to the <see cref="IRecipient{TMessage}"/> interfaces
  128. /// being implemented by <paramref name="recipient"/>. If none are present, this method will do nothing.
  129. /// Note that unlike all other extensions, this method will use reflection to find the handlers to register.
  130. /// Once the registration is complete though, the performance will be exactly the same as with handlers
  131. /// registered directly through any of the other generic extensions for the <see cref="IMessenger"/> interface.
  132. /// </remarks>
  133. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="token"/> are <see langword="null"/>.</exception>
  134. [RequiresUnreferencedCode(
  135. "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
  136. "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
  137. "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
  138. [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
  139. public static void RegisterAll<TToken>(this IMessenger messenger, object recipient, TToken token)
  140. where TToken : IEquatable<TToken>
  141. {
  142. ArgumentNullException.ThrowIfNull(messenger);
  143. ArgumentNullException.ThrowIfNull(recipient);
  144. ArgumentNullException.For<TToken>.ThrowIfNull(token);
  145. // We use this method as a callback for the conditional weak table, which will handle
  146. // thread-safety for us. This first callback will try to find a generated method for the
  147. // target recipient type, and just invoke it to get the delegate to cache and use later.
  148. // In this case we also need to create a generic instantiation of the target method first.
  149. [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
  150. [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
  151. static Action<IMessenger, object, TToken> LoadRegistrationMethodsForType(Type recipientType)
  152. {
  153. if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType &&
  154. extensionsType.GetMethod("CreateAllMessagesRegistratorWithToken", new[] { recipientType }) is MethodInfo methodInfo)
  155. {
  156. MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeof(TToken));
  157. return (Action<IMessenger, object, TToken>)genericMethodInfo.Invoke(null, new object?[] { null })!;
  158. }
  159. return LoadRegistrationMethodsForTypeFallback(recipientType);
  160. }
  161. // Fallback method when a generated method is not found.
  162. // This method is only invoked once per recipient type and token type, so we're not
  163. // worried about making it super efficient, and we can use the LINQ code for clarity.
  164. // The LINQ codegen bloat is not really important for the same reason.
  165. [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
  166. static Action<IMessenger, object, TToken> LoadRegistrationMethodsForTypeFallback(Type recipientType)
  167. {
  168. // Get the collection of validation methods
  169. MethodInfo[] registrationMethods = (
  170. from interfaceType in recipientType.GetInterfaces()
  171. where interfaceType.IsGenericType &&
  172. interfaceType.GetGenericTypeDefinition() == typeof(IRecipient<>)
  173. let messageType = interfaceType.GenericTypeArguments[0]
  174. select MethodInfos.RegisterIRecipient.MakeGenericMethod(messageType, typeof(TToken))).ToArray();
  175. // Short path if there are no message handlers to register
  176. if (registrationMethods.Length == 0)
  177. {
  178. return static (_, _, _) => { };
  179. }
  180. // Input parameters (IMessenger instance, non-generic recipient, token)
  181. ParameterExpression arg0 = Expression.Parameter(typeof(IMessenger));
  182. ParameterExpression arg1 = Expression.Parameter(typeof(object));
  183. ParameterExpression arg2 = Expression.Parameter(typeof(TToken));
  184. // Declare a local resulting from the (RecipientType)recipient cast
  185. UnaryExpression inst1 = Expression.Convert(arg1, recipientType);
  186. // We want a single compiled LINQ expression that executes the registration for all
  187. // the declared message types in the input type. To do so, we create a block with the
  188. // unrolled invocations for the individual message registration (for each IRecipient<T>).
  189. // The code below will generate the following block expression:
  190. // ===============================================================================
  191. // {
  192. // var inst1 = (RecipientType)arg1;
  193. // IMessengerExtensions.Register<T0, TToken>(arg0, inst1, arg2);
  194. // IMessengerExtensions.Register<T1, TToken>(arg0, inst1, arg2);
  195. // ...
  196. // IMessengerExtensions.Register<TN, TToken>(arg0, inst1, arg2);
  197. // }
  198. // ===============================================================================
  199. // We also add an explicit object conversion to cast the input recipient type to
  200. // the actual specific type, so that the exposed message handlers are accessible.
  201. BlockExpression body = Expression.Block(
  202. from registrationMethod in registrationMethods
  203. select Expression.Call(registrationMethod, new Expression[]
  204. {
  205. arg0,
  206. inst1,
  207. arg2
  208. }));
  209. return Expression.Lambda<Action<IMessenger, object, TToken>>(body, arg0, arg1, arg2).Compile();
  210. }
  211. // Get or compute the registration method for the current recipient type.
  212. // As in CommunityToolkit.Diagnostics.TypeExtensions.ToTypeString, we use a lambda
  213. // expression instead of a method group expression to leverage the statically initialized
  214. // delegate and avoid repeated allocations for each invocation of this method.
  215. // For more info on this, see the related issue at https://github.com/dotnet/roslyn/issues/5835.
  216. Action<IMessenger, object, TToken> registrationAction = DiscoveredRecipients<TToken>.RegistrationMethods.GetValue(
  217. recipient.GetType(),
  218. LoadRegistrationMethodsForType);
  219. // Invoke the cached delegate to actually execute the message registration
  220. registrationAction(messenger, recipient, token);
  221. }
  222. /// <summary>
  223. /// Registers a recipient for a given type of message.
  224. /// </summary>
  225. /// <typeparam name="TMessage">The type of message to receive.</typeparam>
  226. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  227. /// <param name="recipient">The recipient that will receive the messages.</param>
  228. /// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
  229. /// <remarks>This method will use the default channel to perform the requested registration.</remarks>
  230. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="recipient"/> are <see langword="null"/>.</exception>
  231. public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
  232. where TMessage : class
  233. {
  234. ArgumentNullException.ThrowIfNull(messenger);
  235. ArgumentNullException.ThrowIfNull(recipient);
  236. if (messenger is WeakReferenceMessenger weakReferenceMessenger)
  237. {
  238. weakReferenceMessenger.Register<TMessage, Unit>(recipient, default);
  239. }
  240. else if (messenger is StrongReferenceMessenger strongReferenceMessenger)
  241. {
  242. strongReferenceMessenger.Register<TMessage, Unit>(recipient, default);
  243. }
  244. else
  245. {
  246. messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, static (r, m) => r.Receive(m));
  247. }
  248. }
  249. /// <summary>
  250. /// Registers a recipient for a given type of message.
  251. /// </summary>
  252. /// <typeparam name="TMessage">The type of message to receive.</typeparam>
  253. /// <typeparam name="TToken">The type of token to identify what channel to use to receive messages.</typeparam>
  254. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  255. /// <param name="recipient">The recipient that will receive the messages.</param>
  256. /// <param name="token">The token indicating what channel to use.</param>
  257. /// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
  258. /// <remarks>This method will use the default channel to perform the requested registration.</remarks>
  259. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="token"/> are <see langword="null"/>.</exception>
  260. public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipient<TMessage> recipient, TToken token)
  261. where TMessage : class
  262. where TToken : IEquatable<TToken>
  263. {
  264. ArgumentNullException.ThrowIfNull(messenger);
  265. ArgumentNullException.ThrowIfNull(recipient);
  266. ArgumentNullException.For<TToken>.ThrowIfNull(token);
  267. if (messenger is WeakReferenceMessenger weakReferenceMessenger)
  268. {
  269. weakReferenceMessenger.Register(recipient, token);
  270. }
  271. else if (messenger is StrongReferenceMessenger strongReferenceMessenger)
  272. {
  273. strongReferenceMessenger.Register(recipient, token);
  274. }
  275. else
  276. {
  277. messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, static (r, m) => r.Receive(m));
  278. }
  279. }
  280. /// <summary>
  281. /// Registers a recipient for a given type of message.
  282. /// </summary>
  283. /// <typeparam name="TMessage">The type of message to receive.</typeparam>
  284. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  285. /// <param name="recipient">The recipient that will receive the messages.</param>
  286. /// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
  287. /// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
  288. /// <remarks>This method will use the default channel to perform the requested registration.</remarks>
  289. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="handler"/> are <see langword="null"/>.</exception>
  290. public static void Register<TMessage>(this IMessenger messenger, object recipient, MessageHandler<object, TMessage> handler)
  291. where TMessage : class
  292. {
  293. ArgumentNullException.ThrowIfNull(messenger);
  294. ArgumentNullException.ThrowIfNull(recipient);
  295. ArgumentNullException.ThrowIfNull(handler);
  296. messenger.Register(recipient, default(Unit), handler);
  297. }
  298. /// <summary>
  299. /// Registers a recipient for a given type of message.
  300. /// </summary>
  301. /// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
  302. /// <typeparam name="TMessage">The type of message to receive.</typeparam>
  303. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  304. /// <param name="recipient">The recipient that will receive the messages.</param>
  305. /// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
  306. /// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
  307. /// <remarks>This method will use the default channel to perform the requested registration.</remarks>
  308. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="handler"/> are <see langword="null"/>.</exception>
  309. public static void Register<TRecipient, TMessage>(this IMessenger messenger, TRecipient recipient, MessageHandler<TRecipient, TMessage> handler)
  310. where TRecipient : class
  311. where TMessage : class
  312. {
  313. ArgumentNullException.ThrowIfNull(messenger);
  314. ArgumentNullException.ThrowIfNull(recipient);
  315. ArgumentNullException.ThrowIfNull(handler);
  316. messenger.Register(recipient, default(Unit), handler);
  317. }
  318. /// <summary>
  319. /// Registers a recipient for a given type of message.
  320. /// </summary>
  321. /// <typeparam name="TMessage">The type of message to receive.</typeparam>
  322. /// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
  323. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
  324. /// <param name="recipient">The recipient that will receive the messages.</param>
  325. /// <param name="token">A token used to determine the receiving channel to use.</param>
  326. /// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
  327. /// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
  328. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="handler"/> are <see langword="null"/>.</exception>
  329. public static void Register<TMessage, TToken>(this IMessenger messenger, object recipient, TToken token, MessageHandler<object, TMessage> handler)
  330. where TMessage : class
  331. where TToken : IEquatable<TToken>
  332. {
  333. ArgumentNullException.ThrowIfNull(messenger);
  334. ArgumentNullException.ThrowIfNull(recipient);
  335. ArgumentNullException.For<TToken>.ThrowIfNull(token);
  336. ArgumentNullException.ThrowIfNull(handler);
  337. messenger.Register(recipient, token, handler);
  338. }
  339. /// <summary>
  340. /// Unregisters a recipient from messages of a given type.
  341. /// </summary>
  342. /// <typeparam name="TMessage">The type of message to stop receiving.</typeparam>
  343. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to unregister the recipient.</param>
  344. /// <param name="recipient">The recipient to unregister.</param>
  345. /// <remarks>
  346. /// This method will unregister the target recipient only from the default channel.
  347. /// If the recipient has no registered handler, this method does nothing.
  348. /// </remarks>
  349. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="recipient"/> are <see langword="null"/>.</exception>
  350. public static void Unregister<TMessage>(this IMessenger messenger, object recipient)
  351. where TMessage : class
  352. {
  353. ArgumentNullException.ThrowIfNull(messenger);
  354. ArgumentNullException.ThrowIfNull(recipient);
  355. messenger.Unregister<TMessage, Unit>(recipient, default);
  356. }
  357. /// <summary>
  358. /// Sends a message of the specified type to all registered recipients.
  359. /// </summary>
  360. /// <typeparam name="TMessage">The type of message to send.</typeparam>
  361. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to send the message.</param>
  362. /// <returns>The message that has been sent.</returns>
  363. /// <remarks>
  364. /// This method is a shorthand for <see cref="Send{TMessage}(IMessenger,TMessage)"/> when the
  365. /// message type exposes a parameterless constructor: it will automatically create
  366. /// a new <typeparamref name="TMessage"/> instance and send that to its recipients.
  367. /// </remarks>
  368. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> is <see langword="null"/>.</exception>
  369. public static TMessage Send<TMessage>(this IMessenger messenger)
  370. where TMessage : class, new()
  371. {
  372. ArgumentNullException.ThrowIfNull(messenger);
  373. return messenger.Send(new TMessage(), default(Unit));
  374. }
  375. /// <summary>
  376. /// Sends a message of the specified type to all registered recipients.
  377. /// </summary>
  378. /// <typeparam name="TMessage">The type of message to send.</typeparam>
  379. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to send the message.</param>
  380. /// <param name="message">The message to send.</param>
  381. /// <returns>The message that was sent (ie. <paramref name="message"/>).</returns>
  382. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="message"/> are <see langword="null"/>.</exception>
  383. public static TMessage Send<TMessage>(this IMessenger messenger, TMessage message)
  384. where TMessage : class
  385. {
  386. ArgumentNullException.ThrowIfNull(messenger);
  387. ArgumentNullException.ThrowIfNull(message);
  388. return messenger.Send(message, default(Unit));
  389. }
  390. /// <summary>
  391. /// Sends a message of the specified type to all registered recipients.
  392. /// </summary>
  393. /// <typeparam name="TMessage">The type of message to send.</typeparam>
  394. /// <typeparam name="TToken">The type of token to identify what channel to use to send the message.</typeparam>
  395. /// <param name="messenger">The <see cref="IMessenger"/> instance to use to send the message.</param>
  396. /// <param name="token">The token indicating what channel to use.</param>
  397. /// <returns>The message that has been sent.</returns>
  398. /// <remarks>
  399. /// This method will automatically create a new <typeparamref name="TMessage"/> instance
  400. /// just like <see cref="Send{TMessage}(IMessenger)"/>, and then send it to the right recipients.
  401. /// </remarks>
  402. /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="token"/> are <see langword="null"/>.</exception>
  403. public static TMessage Send<TMessage, TToken>(this IMessenger messenger, TToken token)
  404. where TMessage : class, new()
  405. where TToken : IEquatable<TToken>
  406. {
  407. ArgumentNullException.ThrowIfNull(messenger);
  408. ArgumentNullException.For<TToken>.ThrowIfNull(token);
  409. return messenger.Send(new TMessage(), token);
  410. }
  411. }