/// <summary> /// Subscribes to the given message type. /// </summary> /// <param name="messageType">The type of the message.</param> /// <param name="actionOnMessage">Action to perform on incoming message.</param> public MessageSubscription Subscribe( Delegate actionOnMessage, Type messageType) { actionOnMessage.EnsureNotNull(nameof(actionOnMessage)); messageType.EnsureNotNull(nameof(messageType)); FirLibMessageHelper.EnsureValidMessageType(messageType); MessageSubscription newOne = new(this, messageType, actionOnMessage); lock (_messageSubscriptionsLock) { if (_messageSubscriptions.ContainsKey(messageType)) { _messageSubscriptions[messageType].Add(newOne); } else { List <MessageSubscription> newList = new(); newList.Add(newOne); _messageSubscriptions[messageType] = newList; } } return(newOne); }
/// <summary> /// Subscribes all receiver-methods of the given target object to this Messenger. /// The messages have to be called OnMessageReceived. /// </summary> /// <param name="targetObject">The target object which is to subscribe.</param> public IEnumerable <MessageSubscription> SubscribeAll(object targetObject) { targetObject.EnsureNotNull(nameof(targetObject)); Type targetObjectType = targetObject.GetType(); List <MessageSubscription> generatedSubscriptions = new(16); try { foreach (MethodInfo actMethod in targetObjectType.GetMethods( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod)) { if (!actMethod.Name.Equals(METHOD_NAME_MESSAGE_RECEIVED)) { continue; } ParameterInfo[] parameters = actMethod.GetParameters(); if (parameters.Length != 1) { continue; } if (!FirLibMessageHelper.ValidateMessageType(parameters[0].ParameterType, out _)) { continue; } Type genericAction = typeof(Action <>); Type delegateType = genericAction.MakeGenericType(parameters[0].ParameterType); generatedSubscriptions.Add(this.Subscribe( actMethod.CreateDelegate(delegateType, targetObject), parameters[0].ParameterType)); } } catch (Exception) { foreach (MessageSubscription actSubscription in generatedSubscriptions) { actSubscription.Unsubscribe(); } generatedSubscriptions.Clear(); } return(generatedSubscriptions); }
/// <summary> /// Sends the given message to all subscribers (sync processing). /// </summary> /// <typeparam name="TMessageType">Type of the message.</typeparam> /// <param name="message">The message to send.</param> /// <param name="isInitialCall">Is this one the initial call to publish? (false if we are coming from async routing)</param> private void PublishInternal <TMessageType>( TMessageType message, bool isInitialCall) { FirLibMessageHelper.EnsureValidMessageTypeAndValue(message); try { // Check whether publish is possible if (_publishCheckBehavior == FirLibMessengerThreadingBehavior.EnsureMainSyncContextOnSyncCalls) { if (!this.CompareSynchronizationContexts()) { throw new FirLibException( "Unable to perform a synchronous publish call because current " + "SynchronizationContext is set wrong. This indicates that the call " + "comes from a wrong thread!"); } } // Check for correct message sources Type currentType = typeof(TMessageType); if (isInitialCall) { string[] possibleSources = s_messageSources.GetOrAdd(currentType, (_) => FirLibMessageHelper.GetPossibleSourceMessengersOfMessageType(currentType)); if (possibleSources.Length > 0) { string mainThreadName = _globalMessengerName; if (string.IsNullOrEmpty(mainThreadName) || (Array.IndexOf(possibleSources, mainThreadName) < 0)) { throw new InvalidOperationException( $"The message of type {currentType.FullName} can only be sent " + $"by the threads [{possibleSources.ToCommaSeparatedString()}]. This Messenger " + $"belongs to the thread {(!string.IsNullOrEmpty(mainThreadName) ? mainThreadName : "(empty)")}, " + "so no publish possible!"); } } } // Perform synchronous message handling List <MessageSubscription> subscriptionsToTrigger = new(20); lock (_messageSubscriptionsLock) { if (_messageSubscriptions.ContainsKey(currentType)) { // Need to copy the list to avoid problems, when the list is changed during the loop and cross thread accesses subscriptionsToTrigger = new List <MessageSubscription>(_messageSubscriptions[currentType]); } } // Trigger all found subscriptions List <Exception>?occurredExceptions = null; for (var loop = 0; loop < subscriptionsToTrigger.Count; loop++) { try { subscriptionsToTrigger[loop].Publish(message); } catch (Exception ex) { occurredExceptions ??= new List <Exception>(); occurredExceptions.Add(ex); } } // Perform further message routing if enabled if (isInitialCall) { // Get information about message routing string[] asyncTargets = s_messagesAsyncTargets.GetOrAdd(currentType, (_) => FirLibMessageHelper.GetAsyncRoutingTargetMessengersOfMessageType(currentType)); string mainThreadName = _globalMessengerName; for (var loop = 0; loop < asyncTargets.Length; loop++) { string actAsyncTargetName = asyncTargets[loop]; if (mainThreadName == actAsyncTargetName) { continue; } if (s_messengersByName.TryGetValue(actAsyncTargetName, out var actAsyncTargetHandler)) { var actSyncContext = actAsyncTargetHandler !._hostSyncContext; if (actSyncContext == null) { continue; } FirLibMessenger innerHandlerForAsyncCall = actAsyncTargetHandler !; actSyncContext.PostAlsoIfNull(() => { innerHandlerForAsyncCall.PublishInternal(message, false); }); } } } // Notify all exceptions occurred during publish progress if (isInitialCall) { if ((occurredExceptions != null) && (occurredExceptions.Count > 0)) { throw new MessagePublishException(typeof(TMessageType), occurredExceptions); } } } catch (Exception ex) { // Check whether we have to throw the exception globally var globalExceptionHandler = CustomPublishExceptionHandler; var doRaise = true; if (globalExceptionHandler != null) { try { doRaise = !globalExceptionHandler(this, ex); } catch { doRaise = true; } } // Raise the exception to inform caller about it if (doRaise) { throw; } } }