Вопрос: Какую актуальную библиотеку можно использовать в роли брокера сообщений внутри .NET приложения?

Лучший ответ:
Решил размяться.

Вот шина:
MessageBus.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace SimpleMessageBus
{
    /// <summary>
    /// Рассылает события заинтересованным подписчикам
    /// в рамках одного приложения.
    /// </summary>
    /// <remarks>Используются WeakReference, чтобы не было утечек памяти
    /// на случай, если кто-то забудет отписаться. Поэтому не рекомендуется
    /// использовать лямбда-обработчики событий.</remarks>
    public static class MessageBus
    {
        private static readonly Dictionary<string, List<WeakReference<Action<string, object>>>> Subscribers =
            new Dictionary<string, List<WeakReference<Action<string, object>>>>();

        /// <summary>
        /// Оформляет подписку на событие
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="handler">Подписчик</param>
        public static void Subscribe(string topic,
            Action<string, object> handler)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            List<WeakReference<Action<string, object>>> handlers;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out handlers))
                    handlers = Subscribers[topic] = new List<WeakReference<Action<string, object>>>();

            lock (handlers)
                handlers.Add(new WeakReference<Action<string, object>>(handler));
        }

        /// <summary>
        /// Отменяет подписку на событие
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="handler">Подписчик</param>
        public static void Unsubscribe(string topic,
            Action<string, object> handler)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            List<WeakReference<Action<string, object>>> list;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out list))
                    return;

            lock (list)
            {
                var i = 0;
                while (i < list.Count)
                {
                    var reference = list[i];
                    if (!reference.TryGetTarget(out var target))
                        list.RemoveAt(i); // Заодно очищаем список от мертвых подписчиков
                    else if (target == handler)
                    {
                        list.RemoveAt(i);
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// Оповещает подписчиков о наступлении события
        /// </summary>
        /// <param name="topic">Тип события</param>
        /// <param name="data">Данные события</param>
        public static void Publish(string topic, object data = null)
        {
            if (String.IsNullOrEmpty(topic))
                throw new ArgumentNullException(nameof(topic));

            List<WeakReference<Action<string, object>>> list;
            lock (Subscribers)
                if (!Subscribers.TryGetValue(topic, out list))
                    return;

            var handlers = new List<Action<string, object>>();
            lock (list)
            {
                var i = 0;
                while (i < list.Count)
                {
                    var reference = list[i];
                    if (!reference.TryGetTarget(out var target))
                        list.RemoveAt(i);
                    else
                    {
                        handlers.Add(target);
                        ++i;
                    }
                }
            }

            // В Unity вызывать обработчики событий, наверное,
            // нужно как-то так:
            UnityEngine.WSA.Application.InvokeOnAppThread(() =>
            // А в обычном консольном приложении так:
            // Task.Run(() =>
            {
                foreach (var handler in handlers)
                {
                    try
                    {
                        handler.Invoke(topic, data);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex);
                    }
                }
            }, false);
        }
    }
}

Вот пример использования:
using System.Diagnostics;
using System.Threading.Tasks;

namespace SimpleMessageBus
{
    public static class Program
    {
        public static async Task Main(string[] args)
        {
            Debug.WriteLine("Started.");

            MessageBus.Subscribe("Rain", RainHandler);
            MessageBus.Publish("Rain");

            await Task.Delay(100); // Ждем, пока событие поступит
            MessageBus.Unsubscribe("Rain", RainHandler);
            
            Debug.WriteLine("Finished.");
        }

        private static void RainHandler(string topic, object data)
        {
            Debug.WriteLine("Event: " + topic);
        }
    }
}

P.S. Бесплатно. Опенсорс.

Upd. Добавил вызов UnityEngine.WSA.Application.InvokeOnAppThread() — все-таки у нас целевая платформа Unity.

Худший ответ:
Посмотри на System.Threading.Channels и rxNET.
И ещё посмотри на ECS, как альтернативный способ избавиться от жуткой лапши