浏览代码

Cleanup code in a few projects

Lucas Trzesniewski 1 年之前
父节点
当前提交
726ab01838
共有 100 个文件被更改,包括 5489 次插入5575 次删除
  1. 3 4
      src/Abc.Zebus.Contracts/ICommand.cs
  2. 3 4
      src/Abc.Zebus.Contracts/IEvent.cs
  3. 3 4
      src/Abc.Zebus.Contracts/IMessage.cs
  4. 8 9
      src/Abc.Zebus.Contracts/MessageTypeIdAttribute.cs
  5. 20 21
      src/Abc.Zebus.Contracts/Routing/Routable.cs
  6. 8 9
      src/Abc.Zebus.Contracts/Routing/RoutingPositionAttribute.cs
  7. 4 5
      src/Abc.Zebus.Contracts/TransientAttribute.cs
  8. 61 62
      src/Abc.Zebus.Log4Net/Log4NetFactory.cs
  9. 37 38
      src/Abc.Zebus.Testing/Comparison/ComparisonExtensions.cs
  10. 76 77
      src/Abc.Zebus.Testing/Comparison/MessageComparer.cs
  11. 26 27
      src/Abc.Zebus.Testing/CurrentThreadTaskScheduler.cs
  12. 103 104
      src/Abc.Zebus.Testing/Directory/TestPeerDirectory.cs
  13. 4 5
      src/Abc.Zebus.Testing/Dispatch/IAsyncExecutableMessage.cs
  14. 4 5
      src/Abc.Zebus.Testing/Dispatch/IExecutableMessage.cs
  15. 26 28
      src/Abc.Zebus.Testing/Dispatch/NoopMessageHandlerInvoker.cs
  16. 41 42
      src/Abc.Zebus.Testing/Dispatch/TestAsyncMessageHandlerInvoker`1.cs
  17. 20 21
      src/Abc.Zebus.Testing/Dispatch/TestBatchedMessageHandlerInvoker`1.cs
  18. 43 44
      src/Abc.Zebus.Testing/Dispatch/TestMessageHandlerInvoker`1.cs
  19. 6 7
      src/Abc.Zebus.Testing/Extensions/ExtendSystemDateTime.cs
  20. 353 354
      src/Abc.Zebus.Testing/Extensions/NUnitExtensions.cs
  21. 146 141
      src/Abc.Zebus.Testing/Integration/IntegrationTestFixture.cs
  22. 134 135
      src/Abc.Zebus.Testing/Integration/TestService.cs
  23. 122 123
      src/Abc.Zebus.Testing/Measurements/Measure.cs
  24. 8 9
      src/Abc.Zebus.Testing/Measurements/MeasureConfiguration.cs
  25. 60 61
      src/Abc.Zebus.Testing/MessageSerializationTester.cs
  26. 324 325
      src/Abc.Zebus.Testing/TestBus.cs
  27. 100 101
      src/Abc.Zebus.Testing/TestExtensions.cs
  28. 103 105
      src/Abc.Zebus.Testing/Transport/TestTransport.cs
  29. 49 51
      src/Abc.Zebus.Testing/Transport/TransportMessageSent.cs
  30. 32 33
      src/Abc.Zebus.Testing/Transport/UpdatedPeer.cs
  31. 33 34
      src/Abc.Zebus.Testing/UnitTesting/MoqExtensions.cs
  32. 31 32
      src/Abc.Zebus.Testing/UnitTesting/SetupSequence.cs
  33. 17 18
      src/Abc.Zebus.Testing/Wait.cs
  34. 52 53
      src/Abc.Zebus/BusExtensions.cs
  35. 47 48
      src/Abc.Zebus/CommandResult.cs
  36. 664 665
      src/Abc.Zebus/Core/Bus.cs
  37. 18 19
      src/Abc.Zebus/Core/BusConfiguration.cs
  38. 131 134
      src/Abc.Zebus/Core/BusFactory.cs
  39. 133 134
      src/Abc.Zebus/Core/BusMessageLogger.cs
  40. 4 5
      src/Abc.Zebus/Core/DefaultMessageSendingStrategy.cs
  41. 7 8
      src/Abc.Zebus/Core/DefaultStoppingStrategy.cs
  42. 4 5
      src/Abc.Zebus/Core/IMessageSendingStrategy.cs
  43. 5 6
      src/Abc.Zebus/Core/IStoppingStrategy.cs
  44. 73 74
      src/Abc.Zebus/Core/MessageContextAwareBus.cs
  45. 79 81
      src/Abc.Zebus/Core/MessageExecutionCompleted.cs
  46. 18 19
      src/Abc.Zebus/Core/RoundRobinPeerSelector.cs
  47. 13 13
      src/Abc.Zebus/Directory/DecommissionPeerCommand.cs
  48. 5 6
      src/Abc.Zebus/Directory/DirectoryErrorCodes.cs
  49. 63 64
      src/Abc.Zebus/Directory/DirectoryPeerSelector.cs
  50. 17 18
      src/Abc.Zebus/Directory/IPeerDirectory.cs
  51. 16 16
      src/Abc.Zebus/Directory/MarkPeerAsNotRespondingCommand.cs
  52. 16 16
      src/Abc.Zebus/Directory/MarkPeerAsRespondingCommand.cs
  53. 30 32
      src/Abc.Zebus/Directory/MessageBinding.cs
  54. 13 13
      src/Abc.Zebus/Directory/PeerDecommissioned.cs
  55. 45 45
      src/Abc.Zebus/Directory/PeerDescriptor.cs
  56. 94 95
      src/Abc.Zebus/Directory/PeerDirectoryClient.PeerEntry.cs
  57. 369 370
      src/Abc.Zebus/Directory/PeerDirectoryClient.cs
  58. 15 15
      src/Abc.Zebus/Directory/PeerNotResponding.cs
  59. 15 15
      src/Abc.Zebus/Directory/PeerResponding.cs
  60. 13 13
      src/Abc.Zebus/Directory/PeerStarted.cs
  61. 22 22
      src/Abc.Zebus/Directory/PeerStopped.cs
  62. 196 197
      src/Abc.Zebus/Directory/PeerSubscriptionTree.cs
  63. 25 25
      src/Abc.Zebus/Directory/PeerSubscriptionsForTypesUpdated.cs
  64. 13 13
      src/Abc.Zebus/Directory/PeerSubscriptionsUpdated.cs
  65. 8 9
      src/Abc.Zebus/Directory/PeerUpdateAction.cs
  66. 5 6
      src/Abc.Zebus/Directory/PingPeerCommand.cs
  67. 13 13
      src/Abc.Zebus/Directory/RegisterPeerCommand.cs
  68. 10 11
      src/Abc.Zebus/Directory/RegisterPeerResponse.cs
  69. 46 47
      src/Abc.Zebus/Directory/SubscriptionsForType.cs
  70. 22 22
      src/Abc.Zebus/Directory/UnregisterPeerCommand.cs
  71. 19 19
      src/Abc.Zebus/Directory/UpdatePeerSubscriptionsCommand.cs
  72. 19 19
      src/Abc.Zebus/Directory/UpdatePeerSubscriptionsForTypesCommand.cs
  73. 50 51
      src/Abc.Zebus/Dispatch/AsyncMessageHandlerInvoker.cs
  74. 51 52
      src/Abc.Zebus/Dispatch/BatchedMessageHandlerInvoker.cs
  75. 290 291
      src/Abc.Zebus/Dispatch/DispatchQueue.cs
  76. 13 15
      src/Abc.Zebus/Dispatch/DispatchQueueFactory.cs
  77. 8 9
      src/Abc.Zebus/Dispatch/DispatchQueueNameAttribute.cs
  78. 11 12
      src/Abc.Zebus/Dispatch/DispatchResult.cs
  79. 40 41
      src/Abc.Zebus/Dispatch/DynamicMessageHandlerInvoker.cs
  80. 5 6
      src/Abc.Zebus/Dispatch/IDispatchQueueFactory.cs
  81. 4 5
      src/Abc.Zebus/Dispatch/IMessageDispatchFactory.cs
  82. 19 20
      src/Abc.Zebus/Dispatch/IMessageDispatcher.cs
  83. 7 8
      src/Abc.Zebus/Dispatch/IMessageHandlerInvocation.cs
  84. 13 14
      src/Abc.Zebus/Dispatch/IMessageHandlerInvoker.cs
  85. 5 6
      src/Abc.Zebus/Dispatch/IProvideDispatchQueueNameForCurrentNamespace.cs
  86. 14 14
      src/Abc.Zebus/Dispatch/LocalDispatch.cs
  87. 50 51
      src/Abc.Zebus/Dispatch/MessageDispatch.cs
  88. 192 193
      src/Abc.Zebus/Dispatch/MessageDispatcher.cs
  89. 66 67
      src/Abc.Zebus/Dispatch/MessageHandlerInvoker.cs
  90. 6 7
      src/Abc.Zebus/Dispatch/MessageHandlerInvokerMode.cs
  91. 60 61
      src/Abc.Zebus/Dispatch/MessageHandlerInvokerSubscriber.cs
  92. 13 14
      src/Abc.Zebus/Dispatch/Pipes/AfterInvokeArgs.cs
  93. 13 14
      src/Abc.Zebus/Dispatch/Pipes/AttributePipeSource.cs
  94. 22 23
      src/Abc.Zebus/Dispatch/Pipes/BeforeInvokeArgs.cs
  95. 9 10
      src/Abc.Zebus/Dispatch/Pipes/IPipe.cs
  96. 7 8
      src/Abc.Zebus/Dispatch/Pipes/IPipeManager.cs
  97. 5 6
      src/Abc.Zebus/Dispatch/Pipes/IPipeSource.cs
  98. 8 9
      src/Abc.Zebus/Dispatch/Pipes/PipeAttribute.cs
  99. 85 86
      src/Abc.Zebus/Dispatch/Pipes/PipeInvocation.cs
  100. 58 59
      src/Abc.Zebus/Dispatch/Pipes/PipeManager.cs

+ 3 - 4
src/Abc.Zebus.Contracts/ICommand.cs

@@ -1,6 +1,5 @@
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+public interface ICommand : IMessage
 {
 {
-    public interface ICommand : IMessage
-    {
-    }
 }
 }

+ 3 - 4
src/Abc.Zebus.Contracts/IEvent.cs

@@ -1,6 +1,5 @@
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+public interface IEvent : IMessage
 {
 {
-    public interface IEvent : IMessage
-    {
-    }
 }
 }

+ 3 - 4
src/Abc.Zebus.Contracts/IMessage.cs

@@ -1,6 +1,5 @@
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+public interface IMessage
 {
 {
-    public interface IMessage
-    {
-    }
 }
 }

+ 8 - 9
src/Abc.Zebus.Contracts/MessageTypeIdAttribute.cs

@@ -1,15 +1,14 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class MessageTypeIdAttribute : Attribute
 {
 {
-    [AttributeUsage(AttributeTargets.Class)]
-    public sealed class MessageTypeIdAttribute : Attribute
-    {
-        public Guid MessageTypeId { get; }
+    public Guid MessageTypeId { get; }
 
 
-        public MessageTypeIdAttribute(string typeId)
-        {
-            MessageTypeId = Guid.Parse(typeId);
-        }
+    public MessageTypeIdAttribute(string typeId)
+    {
+        MessageTypeId = Guid.Parse(typeId);
     }
     }
 }
 }

+ 20 - 21
src/Abc.Zebus.Contracts/Routing/Routable.cs

@@ -1,29 +1,28 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Routing
+namespace Abc.Zebus.Routing;
+
+/// <summary>
+/// Indicates that the target message is routable.
+/// </summary>
+/// <remarks>
+/// <para>
+/// A routable message must have routable members. The routable members must be explicitly specified using <see cref="RoutingPositionAttribute"/>.
+/// </para>
+/// <para>
+/// The default subscription mode of routable message is <c>Manual</c>, i.e.: subscriptions must be manually created with <c>IBus.Subscribe</c>.
+/// If you update an existing message to make it routable, consider specifying <c>AutoSubscribe = true</c> to keep previous subscription mode.
+/// </para>
+/// </remarks>
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class Routable : Attribute
 {
 {
     /// <summary>
     /// <summary>
-    /// Indicates that the target message is routable.
+    /// Indicates whether the default subscription mode for the target message is Auto (<c>AutoSubscribe == true</c>) or Manual.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    /// <para>
-    /// A routable message must have routable members. The routable members must be explicitly specified using <see cref="RoutingPositionAttribute"/>.
-    /// </para>
-    /// <para>
-    /// The default subscription mode of routable message is <c>Manual</c>, i.e.: subscriptions must be manually created with <c>IBus.Subscribe</c>.
-    /// If you update an existing message to make it routable, consider specifying <c>AutoSubscribe = true</c> to keep previous subscription mode.
-    /// </para>
+    /// <see cref="AutoSubscribe"/> configures the default subscription mode for the target message. The subscription mode
+    /// can still be overriden per message handler using the <c>SubscriptionMode</c> attribute.
     /// </remarks>
     /// </remarks>
-    [AttributeUsage(AttributeTargets.Class)]
-    public sealed class Routable : Attribute
-    {
-        /// <summary>
-        /// Indicates whether the default subscription mode for the target message is Auto (<c>AutoSubscribe == true</c>) or Manual.
-        /// </summary>
-        /// <remarks>
-        /// <see cref="AutoSubscribe"/> configures the default subscription mode for the target message. The subscription mode
-        /// can still be overriden per message handler using the <c>SubscriptionMode</c> attribute.
-        /// </remarks>
-        public bool AutoSubscribe { get; set; }
-    }
+    public bool AutoSubscribe { get; set; }
 }
 }

+ 8 - 9
src/Abc.Zebus.Contracts/Routing/RoutingPositionAttribute.cs

@@ -1,15 +1,14 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Routing
+namespace Abc.Zebus.Routing;
+
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+public sealed class RoutingPositionAttribute : Attribute
 {
 {
-    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
-    public sealed class RoutingPositionAttribute : Attribute
-    {
-        public int Position { get; }
+    public int Position { get; }
 
 
-        public RoutingPositionAttribute(int position)
-        {
-            Position = position;
-        }
+    public RoutingPositionAttribute(int position)
+    {
+        Position = position;
     }
     }
 }
 }

+ 4 - 5
src/Abc.Zebus.Contracts/TransientAttribute.cs

@@ -1,9 +1,8 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class TransientAttribute : Attribute
 {
 {
-    [AttributeUsage(AttributeTargets.Class)]
-    public sealed class TransientAttribute : Attribute
-    {
-    }
 }
 }

+ 61 - 62
src/Abc.Zebus.Log4Net/Log4NetFactory.cs

@@ -2,87 +2,86 @@ using System;
 using log4net;
 using log4net;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Log4Net
+namespace Abc.Zebus.Log4Net;
+
+public sealed class Log4NetFactory : ILoggerFactory
 {
 {
-    public sealed class Log4NetFactory : ILoggerFactory
+    public void Dispose()
     {
     {
-        public void Dispose()
-        {
-        }
+    }
 
 
-        public ILogger CreateLogger(string categoryName)
-        {
-            return new Logger(LogManager.GetLogger(categoryName));
-        }
+    public ILogger CreateLogger(string categoryName)
+    {
+        return new Logger(LogManager.GetLogger(categoryName));
+    }
+
+    public void AddProvider(ILoggerProvider provider)
+    {
+    }
 
 
-        public void AddProvider(ILoggerProvider provider)
+    private class Logger : ILogger
+    {
+        private readonly ILog _log;
+
+        public Logger(ILog log)
         {
         {
+            _log = log;
         }
         }
 
 
-        private class Logger : ILogger
+        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
         {
         {
-            private readonly ILog _log;
-
-            public Logger(ILog log)
+            switch (logLevel)
             {
             {
-                _log = log;
-            }
-
-            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
-            {
-                switch (logLevel)
-                {
-                    case LogLevel.Debug:
-                        if (_log.IsDebugEnabled)
-                            _log.Debug(formatter(state, exception), exception);
-                        break;
+                case LogLevel.Debug:
+                    if (_log.IsDebugEnabled)
+                        _log.Debug(formatter(state, exception), exception);
+                    break;
 
 
-                    case LogLevel.Information:
-                        if (_log.IsInfoEnabled)
-                            _log.Info(formatter(state, exception), exception);
-                        break;
+                case LogLevel.Information:
+                    if (_log.IsInfoEnabled)
+                        _log.Info(formatter(state, exception), exception);
+                    break;
 
 
-                    case LogLevel.Warning:
-                        if (_log.IsWarnEnabled)
-                            _log.Warn(formatter(state, exception), exception);
-                        break;
+                case LogLevel.Warning:
+                    if (_log.IsWarnEnabled)
+                        _log.Warn(formatter(state, exception), exception);
+                    break;
 
 
-                    case LogLevel.Error:
-                        if (_log.IsErrorEnabled)
-                            _log.Error(formatter(state, exception), exception);
-                        break;
+                case LogLevel.Error:
+                    if (_log.IsErrorEnabled)
+                        _log.Error(formatter(state, exception), exception);
+                    break;
 
 
-                    case LogLevel.Critical:
-                        if (_log.IsFatalEnabled)
-                            _log.Fatal(formatter(state, exception), exception);
-                        break;
-                }
+                case LogLevel.Critical:
+                    if (_log.IsFatalEnabled)
+                        _log.Fatal(formatter(state, exception), exception);
+                    break;
             }
             }
+        }
 
 
-            public bool IsEnabled(LogLevel logLevel)
+        public bool IsEnabled(LogLevel logLevel)
+        {
+            return logLevel switch
             {
             {
-                return logLevel switch
-                {
-                    LogLevel.Debug       => _log.IsDebugEnabled,
-                    LogLevel.Information => _log.IsInfoEnabled,
-                    LogLevel.Warning     => _log.IsWarnEnabled,
-                    LogLevel.Error       => _log.IsErrorEnabled,
-                    LogLevel.Critical    => _log.IsFatalEnabled,
-                    _                    => false
-                };
-            }
-
-            public IDisposable BeginScope<TState>(TState state)
-                => EmptyDisposable.Instance;
+                LogLevel.Debug       => _log.IsDebugEnabled,
+                LogLevel.Information => _log.IsInfoEnabled,
+                LogLevel.Warning     => _log.IsWarnEnabled,
+                LogLevel.Error       => _log.IsErrorEnabled,
+                LogLevel.Critical    => _log.IsFatalEnabled,
+                _                    => false
+            };
         }
         }
 
 
-        private class EmptyDisposable : IDisposable
-        {
-            public static EmptyDisposable Instance { get; } = new EmptyDisposable();
+        public IDisposable BeginScope<TState>(TState state)
+            => EmptyDisposable.Instance;
+    }
 
 
-            public void Dispose()
-            {
-            }
+    private class EmptyDisposable : IDisposable
+    {
+        public static EmptyDisposable Instance { get; } = new();
+
+        public void Dispose()
+        {
         }
         }
     }
     }
 }
 }

+ 37 - 38
src/Abc.Zebus.Testing/Comparison/ComparisonExtensions.cs

@@ -4,56 +4,55 @@ using KellermanSoftware.CompareNetObjects;
 using KellermanSoftware.CompareNetObjects.TypeComparers;
 using KellermanSoftware.CompareNetObjects.TypeComparers;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Testing.Comparison
+namespace Abc.Zebus.Testing.Comparison;
+
+internal static class ComparisonExtensions
 {
 {
-    internal static class ComparisonExtensions
+    public static bool DeepCompare<T>(this T firstObj, T secondObj, params string[] elementsToIgnore)
     {
     {
-        public static bool DeepCompare<T>(this T firstObj, T secondObj, params string[] elementsToIgnore)
-        {
-            var comparer = CreateComparer();
-            comparer.Config.MembersToIgnore.AddRange(elementsToIgnore);
+        var comparer = CreateComparer();
+        comparer.Config.MembersToIgnore.AddRange(elementsToIgnore);
 
 
-            return comparer.Compare(firstObj, secondObj).AreEqual;
-        }
+        return comparer.Compare(firstObj, secondObj).AreEqual;
+    }
 
 
-        public static CompareLogic CreateComparer()
+    public static CompareLogic CreateComparer()
+    {
+        return new CompareLogic
         {
         {
-            return new CompareLogic
+            Config =
             {
             {
-                Config =
+                CompareStaticProperties = false,
+                CompareStaticFields = false,
+                CustomComparers =
                 {
                 {
-                    CompareStaticProperties = false,
-                    CompareStaticFields = false,
-                    CustomComparers =
-                    {
-                        // TODO : Is this still used?
-                        new EquatableComparer()
-                    },
-                    AttributesToIgnore = new List<Type> { typeof(ProtoIgnoreAttribute) },
-                }
-            };
-        }
+                    // TODO : Is this still used?
+                    new EquatableComparer()
+                },
+                AttributesToIgnore = new List<Type> { typeof(ProtoIgnoreAttribute) },
+            }
+        };
+    }
 
 
-        private class EquatableComparer : BaseTypeComparer
+    private class EquatableComparer : BaseTypeComparer
+    {
+        public EquatableComparer()
+            : base(RootComparerFactory.GetRootComparer())
         {
         {
-            public EquatableComparer()
-                : base(RootComparerFactory.GetRootComparer())
-            {
-            }
+        }
 
 
-            public override bool IsTypeMatch(Type type1, Type type2)
-            {
-                if (type1 != type2)
-                    return false;
+        public override bool IsTypeMatch(Type type1, Type type2)
+        {
+            if (type1 != type2)
+                return false;
 
 
-                return typeof(IEquatable<>).MakeGenericType(type1).IsAssignableFrom(type1);
-            }
+            return typeof(IEquatable<>).MakeGenericType(type1).IsAssignableFrom(type1);
+        }
 
 
-            public override void CompareType(CompareParms parms)
-            {
-                if (!Equals(parms.Object1, parms.Object2))
-                    AddDifference(parms);
-            }
+        public override void CompareType(CompareParms parms)
+        {
+            if (!Equals(parms.Object1, parms.Object2))
+                AddDifference(parms);
         }
         }
     }
     }
 }
 }

+ 76 - 77
src/Abc.Zebus.Testing/Comparison/MessageComparer.cs

@@ -7,108 +7,107 @@ using Newtonsoft.Json;
 using Newtonsoft.Json.Converters;
 using Newtonsoft.Json.Converters;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace Abc.Zebus.Testing.Comparison
+namespace Abc.Zebus.Testing.Comparison;
+
+public class MessageComparer
 {
 {
-    public class MessageComparer
+    private readonly CompareLogic _comparer = ComparisonExtensions.CreateComparer();
+
+    public MessageComparer()
     {
     {
-        private readonly CompareLogic _comparer = ComparisonExtensions.CreateComparer();
+        _comparer.Config.MembersToIgnore.Add("Sourcing");
+    }
 
 
-        public MessageComparer()
-        {
-            _comparer.Config.MembersToIgnore.Add("Sourcing");
-        }
+    public void CheckExpectations(IEnumerable<object> actualMessages, IEnumerable<object> expectedMessages, bool exactMatch)
+    {
+        var differences = GetListsDiff(actualMessages, expectedMessages);
 
 
-        public void CheckExpectations(IEnumerable<object> actualMessages, IEnumerable<object> expectedMessages, bool exactMatch)
-        {
-            var differences = GetListsDiff(actualMessages, expectedMessages);
+        var errorMsg = CreateErrorMessage(differences, exactMatch);
 
 
-            var errorMsg = CreateErrorMessage(differences, exactMatch);
+        if (!string.IsNullOrEmpty(errorMsg))
+            Assert.Fail(errorMsg);
+    }
 
 
-            if (!string.IsNullOrEmpty(errorMsg))
-                Assert.Fail(errorMsg);
-        }
+    private string CreateErrorMessage<TItem>(Differences<TItem> differences, bool exactMatch)
+        where TItem : class
+    {
+        string errorMsg = string.Empty;
 
 
-        private string CreateErrorMessage<TItem>(Differences<TItem> differences, bool exactMatch)
-            where TItem : class
+        foreach (var missing in differences.Missings)
         {
         {
-            string errorMsg = string.Empty;
-
-            foreach (var missing in differences.Missings)
+            errorMsg += $"Missing: {missing.GetType().Name} {SerializeJsonSerializer(missing)} " + Environment.NewLine;
+            if (!exactMatch)
             {
             {
-                errorMsg += $"Missing: {missing.GetType().Name} {SerializeJsonSerializer(missing)} " + Environment.NewLine;
-                if (!exactMatch)
-                {
-                    var candidate = differences.PossibleCandidates.FirstOrDefault(evt => evt.GetType() == missing.GetType());
-                    if (candidate != null)
-                        errorMsg += $"Possible match: {candidate.GetType().Name} {SerializeJsonSerializer(candidate)}" + Environment.NewLine;
-                }
+                var candidate = differences.PossibleCandidates.FirstOrDefault(evt => evt.GetType() == missing.GetType());
+                if (candidate != null)
+                    errorMsg += $"Possible match: {candidate.GetType().Name} {SerializeJsonSerializer(candidate)}" + Environment.NewLine;
             }
             }
+        }
 
 
-            if (exactMatch)
+        if (exactMatch)
+        {
+            foreach (var notExpected in differences.NotExpected)
             {
             {
-                foreach (var notExpected in differences.NotExpected)
-                {
-                    errorMsg += $"Not Expected: {notExpected.GetType().Name} {SerializeJsonSerializer(notExpected)} " + Environment.NewLine;
-                }
+                errorMsg += $"Not Expected: {notExpected.GetType().Name} {SerializeJsonSerializer(notExpected)} " + Environment.NewLine;
             }
             }
-
-            return errorMsg;
         }
         }
 
 
-        private Differences<TItem> GetListsDiff<TItem>(IEnumerable<TItem> actualItems, IEnumerable<TItem> expectedItems)
-            where TItem : class
-        {
-            var notExpectedEvents = new List<TItem>(actualItems);
-            var missingEvents = new List<TItem>();
-            var possibleCandidates = new List<TItem>();
+        return errorMsg;
+    }
 
 
-            foreach (var expected in expectedItems)
+    private Differences<TItem> GetListsDiff<TItem>(IEnumerable<TItem> actualItems, IEnumerable<TItem> expectedItems)
+        where TItem : class
+    {
+        var notExpectedEvents = new List<TItem>(actualItems);
+        var missingEvents = new List<TItem>();
+        var possibleCandidates = new List<TItem>();
+
+        foreach (var expected in expectedItems)
+        {
+            var foundEvent = notExpectedEvents.FirstOrDefault(evt => _comparer.Compare(evt, expected).AreEqual);
+            if (foundEvent != null)
+                notExpectedEvents.Remove(foundEvent);
+            else
             {
             {
-                var foundEvent = notExpectedEvents.FirstOrDefault(evt => _comparer.Compare(evt, expected).AreEqual);
-                if (foundEvent != null)
-                    notExpectedEvents.Remove(foundEvent);
-                else
-                {
-                    missingEvents.Add(expected);
-                    var eventOfSameType = notExpectedEvents.FirstOrDefault(evt => evt.GetType() == expected.GetType());
-                    if (eventOfSameType != null)
-                        possibleCandidates.Add(eventOfSameType);
-                }
+                missingEvents.Add(expected);
+                var eventOfSameType = notExpectedEvents.FirstOrDefault(evt => evt.GetType() == expected.GetType());
+                if (eventOfSameType != null)
+                    possibleCandidates.Add(eventOfSameType);
             }
             }
-
-            return new Differences<TItem>(missingEvents, notExpectedEvents, possibleCandidates);
         }
         }
 
 
-        private string SerializeJsonSerializer(object value)
+        return new Differences<TItem>(missingEvents, notExpectedEvents, possibleCandidates);
+    }
+
+    private string SerializeJsonSerializer(object value)
+    {
+        var stringWriter = new StringWriter();
+        using (var jsonWriter = new JsonTextWriter(stringWriter))
         {
         {
-            var stringWriter = new StringWriter();
-            using (var jsonWriter = new JsonTextWriter(stringWriter))
+            jsonWriter.QuoteName = false;
+            jsonWriter.Formatting = Formatting.Indented;
+            var serializerSettings = new JsonSerializerSettings
             {
             {
-                jsonWriter.QuoteName = false;
-                jsonWriter.Formatting = Formatting.Indented;
-                var serializerSettings = new JsonSerializerSettings
-                {
-                    Converters = new JsonConverter[] { new IsoDateTimeConverter() },
-                    NullValueHandling = NullValueHandling.Ignore,
-                };
-                JsonSerializer.Create(serializerSettings).Serialize(jsonWriter, value);
-            }
-
-            return stringWriter.ToString();
+                Converters = new JsonConverter[] { new IsoDateTimeConverter() },
+                NullValueHandling = NullValueHandling.Ignore,
+            };
+            JsonSerializer.Create(serializerSettings).Serialize(jsonWriter, value);
         }
         }
 
 
-        private class Differences<TItem>
-        {
-            public IEnumerable<TItem> PossibleCandidates { get; private set; }
-            public IEnumerable<TItem> Missings { get; private set; }
-            public IEnumerable<TItem> NotExpected { get; private set; }
+        return stringWriter.ToString();
+    }
 
 
-            public Differences(IEnumerable<TItem> missings, IEnumerable<TItem> notExpected, IEnumerable<TItem> possibleCandidates)
-            {
-                PossibleCandidates = possibleCandidates;
-                Missings = missings;
-                NotExpected = notExpected;
-            }
+    private class Differences<TItem>
+    {
+        public IEnumerable<TItem> PossibleCandidates { get; private set; }
+        public IEnumerable<TItem> Missings { get; private set; }
+        public IEnumerable<TItem> NotExpected { get; private set; }
+
+        public Differences(IEnumerable<TItem> missings, IEnumerable<TItem> notExpected, IEnumerable<TItem> possibleCandidates)
+        {
+            PossibleCandidates = possibleCandidates;
+            Missings = missings;
+            NotExpected = notExpected;
         }
         }
     }
     }
 }
 }

+ 26 - 27
src/Abc.Zebus.Testing/CurrentThreadTaskScheduler.cs

@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved. 
+// Copyright (c) Microsoft Corporation. All rights reserved.
 // http://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364
 // http://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364
 // This code is released under the terms of the new MS LPL licence
 // This code is released under the terms of the new MS LPL licence
 
 
@@ -6,34 +6,33 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace Abc.Zebus.Testing
+namespace Abc.Zebus.Testing;
+
+internal sealed class CurrentThreadTaskScheduler : TaskScheduler
 {
 {
-    internal sealed class CurrentThreadTaskScheduler : TaskScheduler
+    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
+    /// <param name="task">The task to be executed.</param>
+    protected override void QueueTask(Task task)
     {
     {
-        /// <summary>Runs the provided Task synchronously on the current thread.</summary>
-        /// <param name="task">The task to be executed.</param>
-        protected override void QueueTask(Task task)
-        {
-            TryExecuteTask(task);
-        }
-
-        /// <summary>Runs the provided Task synchronously on the current thread.</summary>
-        /// <param name="task">The task to be executed.</param>
-        /// <param name="taskWasPreviouslyQueued">Whether the Task was previously queued to the scheduler.</param>
-        /// <returns>True if the Task was successfully executed; otherwise, false.</returns>
-        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
-        {
-            return TryExecuteTask(task);
-        }
+        TryExecuteTask(task);
+    }
 
 
-        /// <summary>Gets the Tasks currently scheduled to this scheduler.</summary>
-        /// <returns>An empty enumerable, as Tasks are never queued, only executed.</returns>
-        protected override IEnumerable<Task> GetScheduledTasks()
-        {
-            return Enumerable.Empty<Task>();
-        }
+    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
+    /// <param name="task">The task to be executed.</param>
+    /// <param name="taskWasPreviouslyQueued">Whether the Task was previously queued to the scheduler.</param>
+    /// <returns>True if the Task was successfully executed; otherwise, false.</returns>
+    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
+    {
+        return TryExecuteTask(task);
+    }
 
 
-        /// <summary>Gets the maximum degree of parallelism for this scheduler.</summary>
-        public override int MaximumConcurrencyLevel => 1;
+    /// <summary>Gets the Tasks currently scheduled to this scheduler.</summary>
+    /// <returns>An empty enumerable, as Tasks are never queued, only executed.</returns>
+    protected override IEnumerable<Task> GetScheduledTasks()
+    {
+        return Enumerable.Empty<Task>();
     }
     }
-}
+
+    /// <summary>Gets the maximum degree of parallelism for this scheduler.</summary>
+    public override int MaximumConcurrencyLevel => 1;
+}

+ 103 - 104
src/Abc.Zebus.Testing/Directory/TestPeerDirectory.cs

@@ -7,138 +7,137 @@ using Abc.Zebus.Directory;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Testing.Directory
+namespace Abc.Zebus.Testing.Directory;
+
+public class TestPeerDirectory : IPeerDirectory
 {
 {
-    public class TestPeerDirectory : IPeerDirectory
-    {
-        private Subscription[] _initialSubscriptions = Array.Empty<Subscription>();
-        private readonly Dictionary<MessageTypeId, SubscriptionsForType> _dynamicSubscriptions = new Dictionary<MessageTypeId, SubscriptionsForType>();
+    private Subscription[] _initialSubscriptions = Array.Empty<Subscription>();
+    private readonly Dictionary<MessageTypeId, SubscriptionsForType> _dynamicSubscriptions = new();
 
 
-        public TestPeerDirectory()
-        {
-        }
+    public TestPeerDirectory()
+    {
+    }
 
 
-        public event Action Registered = delegate { };
-        public event Action<PeerId, PeerUpdateAction> PeerUpdated = delegate { };
-        public event Action<PeerId, IReadOnlyList<Subscription>> PeerSubscriptionsUpdated = delegate { };
+    public event Action Registered = delegate { };
+    public event Action<PeerId, PeerUpdateAction> PeerUpdated = delegate { };
+    public event Action<PeerId, IReadOnlyList<Subscription>> PeerSubscriptionsUpdated = delegate { };
 
 
-        public ConcurrentDictionary<PeerId, PeerDescriptor> Peers { get; } = new ConcurrentDictionary<PeerId, PeerDescriptor>();
-        public Peer? Self { get; private set; }
+    public ConcurrentDictionary<PeerId, PeerDescriptor> Peers { get; } = new();
+    public Peer? Self { get; private set; }
 
 
-        public TimeSpan TimeSinceLastPing => TimeSpan.Zero;
+    public TimeSpan TimeSinceLastPing => TimeSpan.Zero;
 
 
-        public Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions)
-        {
-            var subscriptionArray = subscriptions.ToArray();
+    public Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions)
+    {
+        var subscriptionArray = subscriptions.ToArray();
 
 
-            Self = self;
-            Self.IsResponding = true;
-            Self.IsUp = true;
-            Peers[self.Id] = self.ToPeerDescriptor(true, subscriptionArray);
+        Self = self;
+        Self.IsResponding = true;
+        Self.IsUp = true;
+        Peers[self.Id] = self.ToPeerDescriptor(true, subscriptionArray);
 
 
-            _initialSubscriptions = subscriptionArray;
+        _initialSubscriptions = subscriptionArray;
 
 
-            Registered();
-            return Task.CompletedTask;
-        }
+        Registered();
+        return Task.CompletedTask;
+    }
 
 
-        public Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes)
+    public Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes)
+    {
+        foreach (var subscriptionsForType in subscriptionsForTypes)
         {
         {
-            foreach (var subscriptionsForType in subscriptionsForTypes)
-            {
-                _dynamicSubscriptions[subscriptionsForType.MessageTypeId] = subscriptionsForType;
-            }
+            _dynamicSubscriptions[subscriptionsForType.MessageTypeId] = subscriptionsForType;
+        }
 
 
-            var newSubscriptions = _initialSubscriptions.Concat(_dynamicSubscriptions.SelectMany(x => x.Value.ToSubscriptions()));
+        var newSubscriptions = _initialSubscriptions.Concat(_dynamicSubscriptions.SelectMany(x => x.Value.ToSubscriptions()));
 
 
-            Peers[Self!.Id] = Self.ToPeerDescriptor(true, newSubscriptions);
-            PeerUpdated(Self.Id, PeerUpdateAction.Updated);
-            return Task.CompletedTask;
-        }
+        Peers[Self!.Id] = Self.ToPeerDescriptor(true, newSubscriptions);
+        PeerUpdated(Self.Id, PeerUpdateAction.Updated);
+        return Task.CompletedTask;
+    }
 
 
-        public Task UnregisterAsync(IBus bus)
-        {
-            _initialSubscriptions = Array.Empty<Subscription>();
-            _dynamicSubscriptions.Clear();
+    public Task UnregisterAsync(IBus bus)
+    {
+        _initialSubscriptions = Array.Empty<Subscription>();
+        _dynamicSubscriptions.Clear();
 
 
-            Peers[Self!.Id] = Self.ToPeerDescriptor(true);
-            PeerUpdated(Self!.Id, PeerUpdateAction.Stopped);
-            return Task.CompletedTask;
-        }
+        Peers[Self!.Id] = Self.ToPeerDescriptor(true);
+        PeerUpdated(Self!.Id, PeerUpdateAction.Stopped);
+        return Task.CompletedTask;
+    }
 
 
-        private readonly Peer _remote = new Peer(new PeerId("remote"), "endpoint");
+    private readonly Peer _remote = new Peer(new PeerId("remote"), "endpoint");
 
 
-        [Obsolete("Use SetupPeer(new PeerId(\"Abc.Remote.0\"), Subscription.Any<TMessage>()) instead")]
-        public void RegisterRemoteListener<TMessage>()
-            where TMessage : IMessage
+    [Obsolete("Use SetupPeer(new PeerId(\"Abc.Remote.0\"), Subscription.Any<TMessage>()) instead")]
+    public void RegisterRemoteListener<TMessage>()
+        where TMessage : IMessage
+    {
+        var peerDescriptor = Peers.GetValueOrAdd(_remote.Id, () => new PeerDescriptor(_remote.Id, _remote.EndPoint, true, _remote.IsUp, _remote.IsResponding, SystemDateTime.UtcNow));
+        var subscriptions = new List<Subscription>(peerDescriptor.Subscriptions)
         {
         {
-            var peerDescriptor = Peers.GetValueOrAdd(_remote.Id, () => new PeerDescriptor(_remote.Id, _remote.EndPoint, true, _remote.IsUp, _remote.IsResponding, SystemDateTime.UtcNow));
-            var subscriptions = new List<Subscription>(peerDescriptor.Subscriptions)
-            {
-                new Subscription(new MessageTypeId(typeof(TMessage)))
-            };
+            new Subscription(new MessageTypeId(typeof(TMessage)))
+        };
 
 
-            peerDescriptor.Subscriptions = subscriptions.ToArray();
-        }
+        peerDescriptor.Subscriptions = subscriptions.ToArray();
+    }
 
 
-        public IList<Peer> GetPeersHandlingMessage(IMessage message)
-        {
-            return GetPeersHandlingMessage(MessageBinding.FromMessage(message));
-        }
+    public IList<Peer> GetPeersHandlingMessage(IMessage message)
+    {
+        return GetPeersHandlingMessage(MessageBinding.FromMessage(message));
+    }
 
 
-        public IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding)
-        {
-            return Peers.Where(x => x.Value.Subscriptions.Any(s => s.Matches(messageBinding))).Select(x => x.Value.Peer).ToList();
-        }
+    public IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding)
+    {
+        return Peers.Where(x => x.Value.Subscriptions.Any(s => s.Matches(messageBinding))).Select(x => x.Value.Peer).ToList();
+    }
 
 
-        public bool IsPersistent(PeerId peerId)
-        {
-            return Peers.TryGetValue(peerId, out var peer) && peer.IsPersistent;
-        }
+    public bool IsPersistent(PeerId peerId)
+    {
+        return Peers.TryGetValue(peerId, out var peer) && peer.IsPersistent;
+    }
 
 
-        public Peer? GetPeer(PeerId peerId)
-        {
-            return Peers.TryGetValue(peerId, out var peerDescriptor) ? peerDescriptor.Peer : null;
-        }
+    public Peer? GetPeer(PeerId peerId)
+    {
+        return Peers.TryGetValue(peerId, out var peerDescriptor) ? peerDescriptor.Peer : null;
+    }
 
 
-        public void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types)
-        {
-        }
+    public void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types)
+    {
+    }
 
 
-        public PeerDescriptor? GetPeerDescriptor(PeerId peerId)
-        {
-            return Peers.TryGetValue(peerId, out var peer)
-                ? peer
-                : null;
-        }
+    public PeerDescriptor? GetPeerDescriptor(PeerId peerId)
+    {
+        return Peers.TryGetValue(peerId, out var peer)
+            ? peer
+            : null;
+    }
 
 
-        public IEnumerable<PeerDescriptor> GetPeerDescriptors()
-        {
-            return Peers.Values;
-        }
+    public IEnumerable<PeerDescriptor> GetPeerDescriptors()
+    {
+        return Peers.Values;
+    }
 
 
-        public IEnumerable<Subscription> GetSelfSubscriptions()
-        {
-            return Self != null && GetPeerDescriptor(Self.Id) is { } descriptor
-                ? descriptor.Subscriptions
-                : Array.Empty<Subscription>();
-        }
+    public IEnumerable<Subscription> GetSelfSubscriptions()
+    {
+        return Self != null && GetPeerDescriptor(Self.Id) is { } descriptor
+            ? descriptor.Subscriptions
+            : Array.Empty<Subscription>();
+    }
 
 
-        public void SetupPeer(PeerId peerId, params Subscription[] subscriptions)
-        {
-            var peerNumber = Peers.Count;
-            var randomPort = 10000 + Peers.Count;
-            SetupPeer(new Peer(peerId, $"tcp://testing-peer-{peerNumber}:{randomPort}"), subscriptions);
-        }
+    public void SetupPeer(PeerId peerId, params Subscription[] subscriptions)
+    {
+        var peerNumber = Peers.Count;
+        var randomPort = 10000 + Peers.Count;
+        SetupPeer(new Peer(peerId, $"tcp://testing-peer-{peerNumber}:{randomPort}"), subscriptions);
+    }
 
 
-        public void SetupPeer(Peer peer, params Subscription[] subscriptions)
-        {
-            var descriptor = Peers.GetOrAdd(peer.Id, _ => peer.ToPeerDescriptor(true));
-            descriptor.Peer.IsResponding = peer.IsResponding;
-            descriptor.Peer.IsUp = peer.IsUp;
-            descriptor.Peer.EndPoint = peer.EndPoint;
-            descriptor.TimestampUtc = DateTime.UtcNow;
-            descriptor.Subscriptions = subscriptions;
-        }
+    public void SetupPeer(Peer peer, params Subscription[] subscriptions)
+    {
+        var descriptor = Peers.GetOrAdd(peer.Id, _ => peer.ToPeerDescriptor(true));
+        descriptor.Peer.IsResponding = peer.IsResponding;
+        descriptor.Peer.IsUp = peer.IsUp;
+        descriptor.Peer.EndPoint = peer.EndPoint;
+        descriptor.TimestampUtc = DateTime.UtcNow;
+        descriptor.Subscriptions = subscriptions;
     }
     }
 }
 }

+ 4 - 5
src/Abc.Zebus.Testing/Dispatch/IAsyncExecutableMessage.cs

@@ -1,10 +1,9 @@
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public interface IAsyncExecutableMessage : IMessage
 {
 {
-    public interface IAsyncExecutableMessage : IMessage
-    {
-        Task ExecuteAsync(IMessageHandlerInvocation invocation);
-    }
+    Task ExecuteAsync(IMessageHandlerInvocation invocation);
 }
 }

+ 4 - 5
src/Abc.Zebus.Testing/Dispatch/IExecutableMessage.cs

@@ -1,9 +1,8 @@
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public interface IExecutableMessage : IMessage
 {
 {
-    public interface IExecutableMessage : IMessage
-    {
-        void Execute(IMessageHandlerInvocation invocation);
-    }
+    void Execute(IMessageHandlerInvocation invocation);
 }
 }

+ 26 - 28
src/Abc.Zebus.Testing/Dispatch/NoopMessageHandlerInvoker.cs

@@ -4,40 +4,38 @@ using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Scan;
 using Abc.Zebus.Scan;
-using Abc.Zebus.Testing.Extensions;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public class NoopMessageHandlerInvoker<THandler, TMessage> : IMessageHandlerInvoker where TMessage : class, IMessage
 {
 {
-    public class NoopMessageHandlerInvoker<THandler, TMessage> : IMessageHandlerInvoker where TMessage : class, IMessage
-    {
-        public Type MessageHandlerType => typeof(THandler);
-        public Type MessageType => typeof(TMessage);
-        public MessageTypeId MessageTypeId => new(MessageType);
-        public string DispatchQueueName => DispatchQueueNameScanner.GetQueueName(typeof(THandler));
-        public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
+    public Type MessageHandlerType => typeof(THandler);
+    public Type MessageType => typeof(TMessage);
+    public MessageTypeId MessageTypeId => new(MessageType);
+    public string DispatchQueueName => DispatchQueueNameScanner.GetQueueName(typeof(THandler));
+    public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions()
-        {
-            return Enumerable.Empty<Subscription>();
-        }
+    public IEnumerable<Subscription> GetStartupSubscriptions()
+    {
+        return Enumerable.Empty<Subscription>();
+    }
 
 
-        public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
-        {
-        }
+    public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+    }
 
 
-        public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
-        {
-            throw new NotSupportedException();
-        }
+    public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+    {
+        throw new NotSupportedException();
+    }
 
 
-        public bool ShouldHandle(IMessage message)
-        {
-            return true;
-        }
+    public bool ShouldHandle(IMessage message)
+    {
+        return true;
+    }
 
 
-        public bool CanMergeWith(IMessageHandlerInvoker other)
-        {
-            return false;
-        }
+    public bool CanMergeWith(IMessageHandlerInvoker other)
+    {
+        return false;
     }
     }
 }
 }

+ 41 - 42
src/Abc.Zebus.Testing/Dispatch/TestAsyncMessageHandlerInvoker`1.cs

@@ -4,63 +4,62 @@ using System.Threading.Tasks;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Scan;
 using Abc.Zebus.Scan;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public class TestAsyncMessageHandlerInvoker<TMessage> : IMessageHandlerInvoker
+    where TMessage : class, IMessage
 {
 {
-    public class TestAsyncMessageHandlerInvoker<TMessage> : IMessageHandlerInvoker
-        where TMessage : class, IMessage
-    {
-        public bool Invoked { get; private set; }
+    public bool Invoked { get; private set; }
 
 
-        public Type MessageHandlerType => typeof(Handler);
-        public Type MessageType => typeof(TMessage);
-        public MessageTypeId MessageTypeId => new(MessageType);
-        public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
+    public Type MessageHandlerType => typeof(Handler);
+    public Type MessageType => typeof(TMessage);
+    public MessageTypeId MessageTypeId => new(MessageType);
+    public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
 
 
-        public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Asynchronous;
+    public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Asynchronous;
 
 
-        public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
-        {
-            throw new NotSupportedException();
-        }
+    public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        throw new NotSupportedException();
+    }
 
 
-        public async Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
-        {
-            Invoked = true;
+    public async Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+    {
+        Invoked = true;
 
 
-            using (invocation.SetupForInvocation())
+        using (invocation.SetupForInvocation())
+        {
+            foreach (var message in invocation.Messages)
             {
             {
-                foreach (var message in invocation.Messages)
-                {
-                    (message as IExecutableMessage)?.Execute(invocation);
+                (message as IExecutableMessage)?.Execute(invocation);
 
 
-                    var asyncTask = (message as IAsyncExecutableMessage)?.ExecuteAsync(invocation);
-                    if (asyncTask != null)
-                        await asyncTask.ConfigureAwait(false);
-                }
+                var asyncTask = (message as IAsyncExecutableMessage)?.ExecuteAsync(invocation);
+                if (asyncTask != null)
+                    await asyncTask.ConfigureAwait(false);
             }
             }
         }
         }
+    }
 
 
-        public bool ShouldHandle(IMessage message)
-        {
-            return true;
-        }
+    public bool ShouldHandle(IMessage message)
+    {
+        return true;
+    }
 
 
-        public bool CanMergeWith(IMessageHandlerInvoker other)
-        {
-            return false;
-        }
+    public bool CanMergeWith(IMessageHandlerInvoker other)
+    {
+        return false;
+    }
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions()
-        {
-            return new[] { new Subscription(MessageTypeId) };
-        }
+    public IEnumerable<Subscription> GetStartupSubscriptions()
+    {
+        return new[] { new Subscription(MessageTypeId) };
+    }
 
 
-        private class Handler : IAsyncMessageHandler<TMessage>
+    private class Handler : IAsyncMessageHandler<TMessage>
+    {
+        public Task Handle(TMessage message)
         {
         {
-            public Task Handle(TMessage message)
-            {
-                throw new NotSupportedException();
-            }
+            throw new NotSupportedException();
         }
         }
     }
     }
 }
 }

+ 20 - 21
src/Abc.Zebus.Testing/Dispatch/TestBatchedMessageHandlerInvoker`1.cs

@@ -3,35 +3,34 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public class TestBatchedMessageHandlerInvoker<TMessage> : BatchedMessageHandlerInvoker
+    where TMessage : class, IEvent
 {
 {
-    public class TestBatchedMessageHandlerInvoker<TMessage> : BatchedMessageHandlerInvoker
-        where TMessage : class, IEvent
+    public TestBatchedMessageHandlerInvoker()
+        : base(null!, typeof(Handler), typeof(TMessage), MessageHandlerInvokerSubscriber.FromAttributes(typeof(Handler)))
     {
     {
-        public TestBatchedMessageHandlerInvoker()
-            : base(null!, typeof(Handler), typeof(TMessage), MessageHandlerInvokerSubscriber.FromAttributes(typeof(Handler)))
-        {
-        }
+    }
 
 
-        public bool Invoked { get; private set; }
+    public bool Invoked { get; private set; }
 
 
-        public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
-        {
-            Invoked = true;
+    public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        Invoked = true;
 
 
-            using (invocation.SetupForInvocation())
-            {
-                var message = invocation.Messages.OfType<IExecutableMessage>().FirstOrDefault();
-                message?.Execute(invocation);
-            }
+        using (invocation.SetupForInvocation())
+        {
+            var message = invocation.Messages.OfType<IExecutableMessage>().FirstOrDefault();
+            message?.Execute(invocation);
         }
         }
+    }
 
 
-        public class Handler : IBatchedMessageHandler<TMessage>
+    public class Handler : IBatchedMessageHandler<TMessage>
+    {
+        public void Handle(IList<TMessage> messages)
         {
         {
-            public void Handle(IList<TMessage> messages)
-            {
-                throw new NotSupportedException();
-            }
+            throw new NotSupportedException();
         }
         }
     }
     }
 }
 }

+ 43 - 44
src/Abc.Zebus.Testing/Dispatch/TestMessageHandlerInvoker`1.cs

@@ -5,64 +5,63 @@ using Abc.Zebus.Dispatch;
 using Abc.Zebus.Scan;
 using Abc.Zebus.Scan;
 using Abc.Zebus.Testing.Extensions;
 using Abc.Zebus.Testing.Extensions;
 
 
-namespace Abc.Zebus.Testing.Dispatch
+namespace Abc.Zebus.Testing.Dispatch;
+
+public class TestMessageHandlerInvoker<TMessage> : IMessageHandlerInvoker where TMessage : class, IMessage
 {
 {
-    public class TestMessageHandlerInvoker<TMessage> : IMessageHandlerInvoker where TMessage : class, IMessage
+    private readonly bool _shouldBeSubscribedOnStartup;
+
+    public TestMessageHandlerInvoker(bool shouldBeSubscribedOnStartup = true)
     {
     {
-        private readonly bool _shouldBeSubscribedOnStartup;
+        _shouldBeSubscribedOnStartup = shouldBeSubscribedOnStartup;
+    }
 
 
-        public TestMessageHandlerInvoker(bool shouldBeSubscribedOnStartup = true)
-        {
-            _shouldBeSubscribedOnStartup = shouldBeSubscribedOnStartup;
-        }
+    public bool Invoked { get; private set; }
 
 
-        public bool Invoked { get; private set; }
+    public Type MessageHandlerType => typeof(Handler);
+    public Type MessageType => typeof(TMessage);
+    public MessageTypeId MessageTypeId => new(MessageType);
+    public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
+    public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
 
 
-        public Type MessageHandlerType => typeof(Handler);
-        public Type MessageType => typeof(TMessage);
-        public MessageTypeId MessageTypeId => new(MessageType);
-        public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
-        public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
+    public IEnumerable<Subscription> GetStartupSubscriptions()
+    {
+        return _shouldBeSubscribedOnStartup
+            ? new[] { new Subscription(MessageTypeId) }
+            : Array.Empty<Subscription>();
+    }
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions()
-        {
-            return _shouldBeSubscribedOnStartup
-                ? new[] { new Subscription(MessageTypeId) }
-                : Array.Empty<Subscription>();
-        }
+    public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        Invoked = true;
 
 
-        public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+        using (invocation.SetupForInvocation())
         {
         {
-            Invoked = true;
-
-            using (invocation.SetupForInvocation())
-            {
-                var message = invocation.Messages.ExpectedSingle() as IExecutableMessage;
-                message?.Execute(invocation);
-            }
+            var message = invocation.Messages.ExpectedSingle() as IExecutableMessage;
+            message?.Execute(invocation);
         }
         }
+    }
 
 
-        public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
-        {
-            throw new NotSupportedException();
-        }
+    public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+    {
+        throw new NotSupportedException();
+    }
 
 
-        public bool ShouldHandle(IMessage message)
-        {
-            return true;
-        }
+    public bool ShouldHandle(IMessage message)
+    {
+        return true;
+    }
 
 
-        public bool CanMergeWith(IMessageHandlerInvoker other)
-        {
-            return false;
-        }
+    public bool CanMergeWith(IMessageHandlerInvoker other)
+    {
+        return false;
+    }
 
 
-        public class Handler : IMessageHandler<TMessage>
+    public class Handler : IMessageHandler<TMessage>
+    {
+        public void Handle(TMessage message)
         {
         {
-            public void Handle(TMessage message)
-            {
-                throw new NotSupportedException();
-            }
+            throw new NotSupportedException();
         }
         }
     }
     }
 }
 }

+ 6 - 7
src/Abc.Zebus.Testing/Extensions/ExtendSystemDateTime.cs

@@ -1,12 +1,11 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Testing.Extensions
+namespace Abc.Zebus.Testing.Extensions;
+
+internal static class ExtendSystemDateTime
 {
 {
-    internal static class ExtendSystemDateTime
+    public static DateTime RoundToMillisecond(this DateTime input)
     {
     {
-        public static DateTime RoundToMillisecond(this DateTime input)
-        {
-            return input.AddTicks(-(input.Ticks % TimeSpan.FromMilliseconds(1).Ticks));
-        }
+        return input.AddTicks(-(input.Ticks % TimeSpan.FromMilliseconds(1).Ticks));
     }
     }
-}
+}

+ 353 - 354
src/Abc.Zebus.Testing/Extensions/NUnitExtensions.cs

@@ -11,461 +11,460 @@ using KellermanSoftware.CompareNetObjects;
 using NUnit.Framework;
 using NUnit.Framework;
 using NUnit.Framework.Constraints;
 using NUnit.Framework.Constraints;
 
 
-namespace Abc.Zebus.Testing.Extensions
+namespace Abc.Zebus.Testing.Extensions;
+
+internal delegate void MethodThatThrows();
+
+[DebuggerStepThrough]
+internal static class NUnitExtensions
 {
 {
-    internal delegate void MethodThatThrows();
+    public static void ShouldBeFalse(this bool condition, string? message = null)
+    {
+        Assert.IsFalse(condition, message);
+    }
 
 
-    [DebuggerStepThrough]
-    internal static class NUnitExtensions
+    public static void ShouldBeTrue(this bool condition, string? message = null)
     {
     {
-        public static void ShouldBeFalse(this bool condition, string? message = null)
-        {
-            Assert.IsFalse(condition, message);
-        }
+        Assert.IsTrue(condition, message);
+    }
 
 
-        public static void ShouldBeTrue(this bool condition, string? message = null)
-        {
-            Assert.IsTrue(condition, message);
-        }
+    public static object ShouldEqual(this object actual, object expected, string? message = null)
+    {
+        Assert.AreEqual(expected, actual, message);
+        return expected;
+    }
 
 
-        public static object ShouldEqual(this object actual, object expected, string? message = null)
-        {
-            Assert.AreEqual(expected, actual, message);
-            return expected;
-        }
+    public static object ShouldEqual(this IEnumerable actual, IEnumerable expected)
+    {
+        CollectionAssert.AreEqual(expected, actual);
+        return expected;
+    }
 
 
-        public static object ShouldEqual(this IEnumerable actual, IEnumerable expected)
-        {
-            CollectionAssert.AreEqual(expected, actual);
-            return expected;
-        }
+    public static void ShouldEqualOneOf<T>(this T actual, params T[] expected)
+    {
+        Assert.That(new[] { actual }, Is.SubsetOf(expected));
+    }
 
 
-        public static void ShouldEqualOneOf<T>(this T actual, params T[] expected)
-        {
-            Assert.That(new[] { actual }, Is.SubsetOf(expected));
-        }
+    public static object ShouldEqualDeeply(this object actual, object expected)
+    {
+        var comparer = ComparisonExtensions.CreateComparer();
+        var result = comparer.Compare(actual, expected);
+        if (!result.AreEqual)
+            Console.WriteLine(result.DifferencesString);
+        Assert.IsTrue(result.AreEqual);
+        return expected;
+    }
 
 
-        public static object ShouldEqualDeeply(this object actual, object expected)
-        {
-            var comparer = ComparisonExtensions.CreateComparer();
-            var result = comparer.Compare(actual, expected);
-            if (!result.AreEqual)
-                Console.WriteLine(result.DifferencesString);
-            Assert.IsTrue(result.AreEqual);
-            return expected;
-        }
+    public static DateTime ShouldApproximateDateTime(this DateTime actual, DateTime expected)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(1).Seconds);
+        return expected;
+    }
 
 
-        public static DateTime ShouldApproximateDateTime(this DateTime actual, DateTime expected)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(1).Seconds);
-            return expected;
-        }
+    public static DateTime ShouldApproximateDateTime(this DateTime actual, DateTime expected, int milliseconds)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(milliseconds).Milliseconds);
+        return expected;
+    }
 
 
-        public static DateTime ShouldApproximateDateTime(this DateTime actual, DateTime expected, int milliseconds)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(milliseconds).Milliseconds);
-            return expected;
-        }
+    public static DateTime? ShouldApproximateDateTime(this DateTime? actual, DateTime expected)
+    {
+        actual.ShouldNotBeNull();
+        return ShouldApproximateDateTime(actual!.Value, expected);
+    }
 
 
-        public static DateTime? ShouldApproximateDateTime(this DateTime? actual, DateTime expected)
-        {
-            actual.ShouldNotBeNull();
-            return ShouldApproximateDateTime(actual!.Value, expected);
-        }
+    public static object ShouldEqualEpsilon(this decimal actual, decimal expected, decimal epsilon)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
 
 
-        public static object ShouldEqualEpsilon(this decimal actual, decimal expected, decimal epsilon)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
+        return expected;
+    }
 
 
-            return expected;
-        }
+    public static object? ShouldEqualEpsilon(this decimal? actual, decimal? expected, decimal epsilon)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
 
 
-        public static object? ShouldEqualEpsilon(this decimal? actual, decimal? expected, decimal epsilon)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
+        return expected;
+    }
 
 
-            return expected;
-        }
+    public static object? ShouldEqualEpsilon(this decimal actual, decimal? expected, decimal epsilon)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
 
 
-        public static object? ShouldEqualEpsilon(this decimal actual, decimal? expected, decimal epsilon)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
+        return expected;
+    }
 
 
-            return expected;
-        }
+    public static object ShouldEqualEpsilon(this double actual, double expected, double epsilon)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
 
 
-        public static object ShouldEqualEpsilon(this double actual, double expected, double epsilon)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
+        return expected;
+    }
 
 
-            return expected;
-        }
+    public static object? ShouldEqualEpsilon(this double? actual, double? expected, double epsilon)
+    {
+        Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
 
 
-        public static object? ShouldEqualEpsilon(this double? actual, double? expected, double epsilon)
-        {
-            Assert.That(actual, Is.EqualTo(expected).Within(epsilon));
+        return expected;
+    }
 
 
-            return expected;
-        }
+    public static object ShouldNotEqual(this object actual, object expected)
+    {
+        Assert.AreNotEqual(expected, actual);
+        return expected;
+    }
 
 
-        public static object ShouldNotEqual(this object actual, object expected)
-        {
-            Assert.AreNotEqual(expected, actual);
-            return expected;
-        }
+    public static void ShouldBeNull(this object? anObject, string? message = null)
+    {
+        Assert.IsNull(anObject, message);
+    }
 
 
-        public static void ShouldBeNull(this object? anObject, string? message = null)
-        {
-            Assert.IsNull(anObject, message);
-        }
+    [ContractAnnotation("anObject: null => halt")]
+    public static void ShouldNotBeNull(this object? anObject, string? message = null)
+    {
+        Assert.IsNotNull(anObject, message);
+    }
 
 
-        [ContractAnnotation("anObject: null => halt")]
-        public static void ShouldNotBeNull(this object? anObject, string? message = null)
-        {
-            Assert.IsNotNull(anObject, message);
-        }
+    public static object ShouldBeTheSameAs(this object actual, object expected)
+    {
+        Assert.AreSame(expected, actual);
+        return expected;
+    }
 
 
-        public static object ShouldBeTheSameAs(this object actual, object expected)
-        {
-            Assert.AreSame(expected, actual);
-            return expected;
-        }
+    public static object ShouldNotBeTheSameAs(this object actual, object expected)
+    {
+        Assert.AreNotSame(expected, actual);
+        return expected;
+    }
 
 
-        public static object ShouldNotBeTheSameAs(this object actual, object expected)
-        {
-            Assert.AreNotSame(expected, actual);
-            return expected;
-        }
+    public static T ShouldBe<T>(this object? actual)
+    {
+        Assert.IsInstanceOf<T>(actual);
+        return (T)actual!;
+    }
 
 
-        public static T ShouldBe<T>(this object? actual)
-        {
-            Assert.IsInstanceOf<T>(actual);
-            return (T)actual!;
-        }
+    public static void ShouldNotBeOfType<T>(this object? actual)
+    {
+        Assert.IsNotInstanceOf<T>(actual);
+    }
 
 
-        public static void ShouldNotBeOfType<T>(this object? actual)
+    public static void ShouldContain(this IEnumerable actual, object expected)
+    {
+        foreach (var obj in actual)
         {
         {
-            Assert.IsNotInstanceOf<T>(actual);
+            var empty = Tolerance.Default;
+            if (new NUnitEqualityComparer().AreEqual(obj, expected, ref empty))
+                return;
         }
         }
 
 
-        public static void ShouldContain(this IEnumerable actual, object expected)
-        {
-            foreach (var obj in actual)
-            {
-                var empty = Tolerance.Default;
-                if (new NUnitEqualityComparer().AreEqual(obj, expected, ref empty))
-                    return;
-            }
-
-            Assert.Fail("'{0}' is not present in the enumerable", expected);
-        }
+        Assert.Fail("'{0}' is not present in the enumerable", expected);
+    }
 
 
-        public static void ShouldContain<T>(this IEnumerable<T> actual, Expression<Func<T, bool>> predicate)
-        {
-            if (!actual.Any(predicate.Compile()))
-                Assert.Fail("no element found matching " + predicate);
-        }
+    public static void ShouldContain<T>(this IEnumerable<T> actual, Expression<Func<T, bool>> predicate)
+    {
+        if (!actual.Any(predicate.Compile()))
+            Assert.Fail("no element found matching " + predicate);
+    }
 
 
-        public static void ShouldNotContain<T>(this IEnumerable<T> actual, Expression<Func<T, bool>> predicate)
-        {
-            if (actual.Any(predicate.Compile()))
-                Assert.Fail("element found matching " + predicate);
-        }
+    public static void ShouldNotContain<T>(this IEnumerable<T> actual, Expression<Func<T, bool>> predicate)
+    {
+        if (actual.Any(predicate.Compile()))
+            Assert.Fail("element found matching " + predicate);
+    }
 
 
-        public static void ShouldNotContain(this IEnumerable actual, object expected)
+    public static void ShouldNotContain(this IEnumerable actual, object expected)
+    {
+        foreach (var obj in actual)
         {
         {
-            foreach (var obj in actual)
-            {
-                var empty = Tolerance.Default;
-                if (new NUnitEqualityComparer().AreEqual(obj, expected, ref empty))
-                    Assert.Fail("'{0}' is present in the enumerable", expected);
-            }
+            var empty = Tolerance.Default;
+            if (new NUnitEqualityComparer().AreEqual(obj, expected, ref empty))
+                Assert.Fail("'{0}' is present in the enumerable", expected);
         }
         }
+    }
 
 
-        public static void ShouldBeEquivalentTo<T>(this IEnumerable<T> collection, params T[] expected)
-        {
-            ShouldBeEquivalentTo((IEnumerable)collection, expected);
-        }
+    public static void ShouldBeEquivalentTo<T>(this IEnumerable<T> collection, params T[] expected)
+    {
+        ShouldBeEquivalentTo((IEnumerable)collection, expected);
+    }
 
 
-        public static void ShouldBeEquivalentTo(this IEnumerable collection, IEnumerable expected, bool compareDeeply = false)
+    public static void ShouldBeEquivalentTo(this IEnumerable collection, IEnumerable expected, bool compareDeeply = false)
+    {
+        if (compareDeeply)
         {
         {
-            if (compareDeeply)
-            {
-                var compareLogic = new CompareLogic();
-                collection.ShouldBeEquivalentTo(expected, (a, b) => compareLogic.Compare(a, b).AreEqual);
-            }
-            else
-                Assert.That(collection, Is.EquivalentTo(expected));
+            var compareLogic = new CompareLogic();
+            collection.ShouldBeEquivalentTo(expected, (a, b) => compareLogic.Compare(a, b).AreEqual);
         }
         }
+        else
+            Assert.That(collection, Is.EquivalentTo(expected));
+    }
 
 
-        public static void ShouldBeOrdered(this IEnumerable collection)
-        {
-            Assert.That(collection, Is.Ordered);
-        }
+    public static void ShouldBeOrdered(this IEnumerable collection)
+    {
+        Assert.That(collection, Is.Ordered);
+    }
 
 
-        public static void ShouldBeEquivalentTo(this IEnumerable collection, IEnumerable expected, Func<object?, object?, bool> comparer)
-        {
-            Assert.That(collection, Is.EquivalentTo(expected).Using(new EqualityComparer(comparer)));
-        }
+    public static void ShouldBeEquivalentTo(this IEnumerable collection, IEnumerable expected, Func<object?, object?, bool> comparer)
+    {
+        Assert.That(collection, Is.EquivalentTo(expected).Using(new EqualityComparer(comparer)));
+    }
 
 
-        public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2)
-        {
-            Assert.Greater(arg1, arg2);
-            return arg2;
-        }
+    public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2)
+    {
+        Assert.Greater(arg1, arg2);
+        return arg2;
+    }
 
 
-        public static IComparable ShouldBeGreaterOrEqualThan(this IComparable arg1, IComparable arg2)
-        {
-            Assert.GreaterOrEqual(arg1, arg2);
-            return arg2;
-        }
+    public static IComparable ShouldBeGreaterOrEqualThan(this IComparable arg1, IComparable arg2)
+    {
+        Assert.GreaterOrEqual(arg1, arg2);
+        return arg2;
+    }
 
 
-        public static IComparable ShouldBeLessOrEqualThan(this IComparable arg1, IComparable arg2, string? message = null)
-        {
-            Assert.LessOrEqual(arg1, arg2, message);
-            return arg2;
-        }
+    public static IComparable ShouldBeLessOrEqualThan(this IComparable arg1, IComparable arg2, string? message = null)
+    {
+        Assert.LessOrEqual(arg1, arg2, message);
+        return arg2;
+    }
 
 
-        public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2)
-        {
-            Assert.Less(arg1, arg2);
-            return arg2;
-        }
+    public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2)
+    {
+        Assert.Less(arg1, arg2);
+        return arg2;
+    }
 
 
-        public static void ShouldBeEmpty<T>(this IEnumerable<T> enumerable, string? message = null)
-        {
-            Assert.IsFalse(enumerable.Any(), message ?? "the collection is not empty");
-        }
+    public static void ShouldBeEmpty<T>(this IEnumerable<T> enumerable, string? message = null)
+    {
+        Assert.IsFalse(enumerable.Any(), message ?? "the collection is not empty");
+    }
 
 
-        public static void ShouldNotBeEmpty<T>(this IEnumerable<T> enumerable, string? message = null)
-        {
-            Assert.IsTrue(enumerable.Any(), message ?? "the collection is empty");
-        }
+    public static void ShouldNotBeEmpty<T>(this IEnumerable<T> enumerable, string? message = null)
+    {
+        Assert.IsTrue(enumerable.Any(), message ?? "the collection is empty");
+    }
 
 
-        public static void ShouldBeEmpty(this string aString)
-        {
-            Assert.IsEmpty(aString);
-        }
+    public static void ShouldBeEmpty(this string aString)
+    {
+        Assert.IsEmpty(aString);
+    }
 
 
-        public static void ShouldNotBeEmpty(this IEnumerable collection, string? message = null)
-        {
-            Assert.IsNotEmpty(collection, message);
-        }
+    public static void ShouldNotBeEmpty(this IEnumerable collection, string? message = null)
+    {
+        Assert.IsNotEmpty(collection, message);
+    }
 
 
-        public static void ShouldNotBeEmpty(this string aString)
-        {
-            Assert.IsNotEmpty(aString);
-        }
+    public static void ShouldNotBeEmpty(this string aString)
+    {
+        Assert.IsNotEmpty(aString);
+    }
+
+    public static void ShouldContain(this string actual, string expected)
+    {
+        Assert.That(actual, Does.Contain(expected));
+    }
 
 
-        public static void ShouldContain(this string actual, string expected)
+    public static void ShouldContainIgnoreCase(this string actual, string expected)
+    {
+        Assert.That(actual, Does.Contain(expected).IgnoreCase);
+    }
+
+    public static void ShouldNotContain(this string actual, string expected)
+    {
+        try
         {
         {
-            Assert.That(actual, Does.Contain(expected));
+            StringAssert.Contains(expected, actual);
         }
         }
-
-        public static void ShouldContainIgnoreCase(this string actual, string expected)
+        catch (AssertionException)
         {
         {
-            Assert.That(actual, Does.Contain(expected).IgnoreCase);
+            return;
         }
         }
 
 
-        public static void ShouldNotContain(this string actual, string expected)
-        {
-            try
-            {
-                StringAssert.Contains(expected, actual);
-            }
-            catch (AssertionException)
-            {
-                return;
-            }
+        throw new AssertionException($"\"{actual}\" should not contain \"{expected}\".");
+    }
 
 
-            throw new AssertionException($"\"{actual}\" should not contain \"{expected}\".");
-        }
+    public static string ShouldBeEqualIgnoringCase(this string actual, string expected)
+    {
+        StringAssert.AreEqualIgnoringCase(expected, actual);
+        return expected;
+    }
 
 
-        public static string ShouldBeEqualIgnoringCase(this string actual, string expected)
-        {
-            StringAssert.AreEqualIgnoringCase(expected, actual);
-            return expected;
-        }
+    public static void ShouldStartWith(this string actual, string expected)
+    {
+        StringAssert.StartsWith(expected, actual);
+    }
 
 
-        public static void ShouldStartWith(this string actual, string expected)
-        {
-            StringAssert.StartsWith(expected, actual);
-        }
+    public static void ShouldEndWith(this string actual, string expected)
+    {
+        StringAssert.EndsWith(expected, actual);
+    }
 
 
-        public static void ShouldEndWith(this string actual, string expected)
-        {
-            StringAssert.EndsWith(expected, actual);
-        }
+    public static void ShouldBeSurroundedWith(this string actual, string expectedStartDelimiter, string expectedEndDelimiter)
+    {
+        StringAssert.StartsWith(expectedStartDelimiter, actual);
+        StringAssert.EndsWith(expectedEndDelimiter, actual);
+    }
 
 
-        public static void ShouldBeSurroundedWith(this string actual, string expectedStartDelimiter, string expectedEndDelimiter)
-        {
-            StringAssert.StartsWith(expectedStartDelimiter, actual);
-            StringAssert.EndsWith(expectedEndDelimiter, actual);
-        }
+    public static void ShouldBeSurroundedWith(this string actual, string expectedDelimiter)
+    {
+        StringAssert.StartsWith(expectedDelimiter, actual);
+        StringAssert.EndsWith(expectedDelimiter, actual);
+    }
 
 
-        public static void ShouldBeSurroundedWith(this string actual, string expectedDelimiter)
-        {
-            StringAssert.StartsWith(expectedDelimiter, actual);
-            StringAssert.EndsWith(expectedDelimiter, actual);
-        }
+    public static void ShouldContainErrorMessage(this Exception exception, string expected)
+    {
+        StringAssert.Contains(expected, exception.Message);
+    }
 
 
-        public static void ShouldContainErrorMessage(this Exception exception, string expected)
-        {
-            StringAssert.Contains(expected, exception.Message);
-        }
+    public static Exception ShouldBeThrownBy(this Type exceptionType, MethodThatThrows method)
+    {
+        var exception = method.GetException();
 
 
-        public static Exception ShouldBeThrownBy(this Type exceptionType, MethodThatThrows method)
-        {
-            var exception = method.GetException();
+        Assert.IsNotNull(exception, $"{exceptionType.Name} was not thrown");
+        Assert.AreEqual(exceptionType, exception!.GetType());
 
 
-            Assert.IsNotNull(exception, $"{exceptionType.Name} was not thrown");
-            Assert.AreEqual(exceptionType, exception!.GetType());
+        return exception!;
+    }
 
 
-            return exception!;
-        }
+    public static void ShouldBeBetween(this DateTime actual, DateTime inferior, DateTime superior)
+    {
+        Assert.LessOrEqual(inferior, actual);
+        Assert.GreaterOrEqual(superior, actual);
+    }
 
 
-        public static void ShouldBeBetween(this DateTime actual, DateTime inferior, DateTime superior)
-        {
-            Assert.LessOrEqual(inferior, actual);
-            Assert.GreaterOrEqual(superior, actual);
-        }
+    public static Exception? GetException(this MethodThatThrows method)
+    {
+        Exception? exception = null;
 
 
-        public static Exception? GetException(this MethodThatThrows method)
+        try
         {
         {
-            Exception? exception = null;
-
-            try
-            {
-                method();
-            }
-            catch (Exception e)
-            {
-                exception = e;
-            }
-
-            return exception;
+            method();
         }
         }
-
-        public static void ShouldBeOfType<T>(this object? actual)
+        catch (Exception e)
         {
         {
-            Assert.IsInstanceOf<T>(actual);
+            exception = e;
         }
         }
 
 
-        public static void ShouldHaveSamePropertiesAs(this object actual, object expected, params string[] ignoredProperties)
-        {
-            var comparer = ComparisonExtensions.CreateComparer();
-            comparer.Config.MembersToIgnore.AddRange(ignoredProperties);
+        return exception;
+    }
 
 
-            var result = comparer.Compare(actual, expected);
-            if (!result.AreEqual)
-                Assert.Fail("Properties should be equal, invalid properties: " + result.DifferencesString);
-        }
+    public static void ShouldBeOfType<T>(this object? actual)
+    {
+        Assert.IsInstanceOf<T>(actual);
+    }
 
 
-        public static void ShouldHaveApproximatePropertiesAs(this object actual, object expected, params string[] ignoredProperties)
-        {
-            var expectedProperties = expected.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToDictionary(x => x.Name);
-            foreach (var actualProperty in actual.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
-            {
-                if (ignoredProperties.Contains(actualProperty.Name))
-                    continue;
+    public static void ShouldHaveSamePropertiesAs(this object actual, object expected, params string[] ignoredProperties)
+    {
+        var comparer = ComparisonExtensions.CreateComparer();
+        comparer.Config.MembersToIgnore.AddRange(ignoredProperties);
 
 
-                var actualValue = actualProperty.GetValue(actual);
-                object? expectedValue = null;
+        var result = comparer.Compare(actual, expected);
+        if (!result.AreEqual)
+            Assert.Fail("Properties should be equal, invalid properties: " + result.DifferencesString);
+    }
 
 
-                if (expectedProperties.TryGetValue(actualProperty.Name, out var expectedProperty))
-                    expectedValue = expectedProperty.GetValue(expected);
+    public static void ShouldHaveApproximatePropertiesAs(this object actual, object expected, params string[] ignoredProperties)
+    {
+        var expectedProperties = expected.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToDictionary(x => x.Name);
+        foreach (var actualProperty in actual.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
+        {
+            if (ignoredProperties.Contains(actualProperty.Name))
+                continue;
 
 
-                if (actualValue == null && expectedValue == null)
-                    continue;
+            var actualValue = actualProperty.GetValue(actual);
+            object? expectedValue = null;
 
 
-                if (expectedValue == null)
-                    Assert.Fail("Missing field or property {0} on type {1}", actualProperty.Name, expected.GetType());
+            if (expectedProperties.TryGetValue(actualProperty.Name, out var expectedProperty))
+                expectedValue = expectedProperty.GetValue(expected);
 
 
-                if (!LooseEquals(expectedValue, actualValue))
-                    Assert.Fail("{0} should be equal, found {1}, expected {2}", actualProperty.Name, actualValue, expectedValue);
-            }
-        }
+            if (actualValue == null && expectedValue == null)
+                continue;
 
 
-        public static void ShouldHaveSize<T>(this IEnumerable<T> enumerable, int size)
-        {
-            enumerable.Count().ShouldEqual(size, $"Collection should contain {size} items");
+            if (expectedValue == null)
+                Assert.Fail("Missing field or property {0} on type {1}", actualProperty.Name, expected.GetType());
+
+            if (!LooseEquals(expectedValue, actualValue))
+                Assert.Fail("{0} should be equal, found {1}, expected {2}", actualProperty.Name, actualValue, expectedValue);
         }
         }
+    }
 
 
-        public static TSource ExpectedSingle<TSource>(this IEnumerable<TSource> source)
-        {
-            var items = source.ToList();
-            items.Count.ShouldEqual(1, "Collection should contain only one item");
+    public static void ShouldHaveSize<T>(this IEnumerable<T> enumerable, int size)
+    {
+        enumerable.Count().ShouldEqual(size, $"Collection should contain {size} items");
+    }
 
 
-            return items[0];
-        }
+    public static TSource ExpectedSingle<TSource>(this IEnumerable<TSource> source)
+    {
+        var items = source.ToList();
+        items.Count.ShouldEqual(1, "Collection should contain only one item");
 
 
-        public static TSource ExpectedSingle<TSource>(this IEnumerable<TSource> source, Expression<Func<TSource, bool>> predicate)
-        {
-            var items = source.Where(predicate.Compile()).ToList();
-            items.Count.ShouldEqual(1, "Collection should contain only one item matching " + predicate);
+        return items[0];
+    }
 
 
-            return items[0];
-        }
+    public static TSource ExpectedSingle<TSource>(this IEnumerable<TSource> source, Expression<Func<TSource, bool>> predicate)
+    {
+        var items = source.Where(predicate.Compile()).ToList();
+        items.Count.ShouldEqual(1, "Collection should contain only one item matching " + predicate);
 
 
-        public static TSource ExpectedFirst<TSource>(this IEnumerable<TSource> source)
-        {
-            var items = source.ToList();
-            Assert.That(items, Is.Not.Empty);
+        return items[0];
+    }
 
 
-            return items[0];
-        }
+    public static TSource ExpectedFirst<TSource>(this IEnumerable<TSource> source)
+    {
+        var items = source.ToList();
+        Assert.That(items, Is.Not.Empty);
 
 
-        private static bool LooseEquals(object? x, object? y)
-        {
-            if (x == y)
-                return true;
+        return items[0];
+    }
 
 
-            if (x == null || y == null)
-                return false;
+    private static bool LooseEquals(object? x, object? y)
+    {
+        if (x == y)
+            return true;
 
 
-            if (x.Equals(y))
-                return true;
+        if (x == null || y == null)
+            return false;
 
 
-            if ((x is DateTime) && (y is DateTime))
-                return ((DateTime)x - (DateTime)y).Duration() <= TimeSpan.FromSeconds(1);
+        if (x.Equals(y))
+            return true;
 
 
-            if ((x is int) && (y is bool))
-                return ((int)x == 1) == (bool)y;
+        if ((x is DateTime) && (y is DateTime))
+            return ((DateTime)x - (DateTime)y).Duration() <= TimeSpan.FromSeconds(1);
 
 
-            if ((x is bool) && (y is int))
-                return ((int)y == 1) == (bool)x;
+        if ((x is int) && (y is bool))
+            return ((int)x == 1) == (bool)y;
 
 
-            try
-            {
-                if (Equals(Convert.ChangeType(x, y.GetType()), y))
-                    return true;
-            }
-            catch
-            {
-                return false;
-            }
+        if ((x is bool) && (y is int))
+            return ((int)y == 1) == (bool)x;
 
 
+        try
+        {
+            if (Equals(Convert.ChangeType(x, y.GetType()), y))
+                return true;
+        }
+        catch
+        {
             return false;
             return false;
         }
         }
 
 
-        private class EqualityComparer : IEqualityComparer
+        return false;
+    }
+
+    private class EqualityComparer : IEqualityComparer
+    {
+        private readonly Func<object?, object?, bool> _comparer;
+
+        public EqualityComparer(Func<object?, object?, bool> comparer)
+        {
+            _comparer = comparer;
+        }
+
+        bool IEqualityComparer.Equals(object? x, object? y)
+        {
+            return _comparer(x, y);
+        }
+
+        public int GetHashCode(object obj)
         {
         {
-            private readonly Func<object?, object?, bool> _comparer;
-
-            public EqualityComparer(Func<object?, object?, bool> comparer)
-            {
-                _comparer = comparer;
-            }
-
-            bool IEqualityComparer.Equals(object? x, object? y)
-            {
-                return _comparer(x, y);
-            }
-
-            public int GetHashCode(object obj)
-            {
-                return obj.GetHashCode();
-            }
+            return obj.GetHashCode();
         }
         }
     }
     }
 }
 }

+ 146 - 141
src/Abc.Zebus.Testing/Integration/IntegrationTestFixture.cs

@@ -11,182 +11,187 @@ using NUnit.Framework;
 
 
 #nullable disable
 #nullable disable
 
 
-namespace Abc.Zebus.Testing.Integration
+namespace Abc.Zebus.Testing.Integration;
+
+[TestFixture]
+[Platform("Win")]
+public abstract class IntegrationTestFixture
 {
 {
-    [TestFixture]
-    [Platform("Win")]
-    public abstract class IntegrationTestFixture
+    private IBus _controlBus;
+    private static TimeSpan _controlBusActionsTimeout = 40.Seconds();
+    private TestService _directoryServiceController;
+    private Stopwatch _testStopwatch;
+
+    [SetUp]
+    public void Setup()
     {
     {
-        private IBus _controlBus;
-        private static TimeSpan _controlBusActionsTimeout = 40.Seconds();
-        private TestService _directoryServiceController;
-        private Stopwatch _testStopwatch;
+        KillInstance(@"\\integration_tests");
+        _testStopwatch = Stopwatch.StartNew();
 
 
-        [SetUp]
-        public void Setup()
-        {
-            KillInstance(@"\\integration_tests");
-            _testStopwatch = Stopwatch.StartNew();
+        _directoryServiceController = CreateDirectoryServiceController();
+        _directoryServiceController.Build();
+        _directoryServiceController.Start();
 
 
-            _directoryServiceController = CreateDirectoryServiceController();
-            _directoryServiceController.Build();
-            _directoryServiceController.Start();
+        _controlBus = CreateAndStartControlBus();
+    }
 
 
-            _controlBus = CreateAndStartControlBus();
-        }
+    [TearDown]
+    public void Teardown()
+    {
+        _controlBus.Stop();
+        _directoryServiceController.Stop();
+        _directoryServiceController.Dispose();
 
 
-        [TearDown]
-        public void Teardown()
-        {
-            _controlBus.Stop();
-            _directoryServiceController.Stop();
-            _directoryServiceController.Dispose();
+        Log("Integration test lasted " + _testStopwatch.Elapsed);
+        KillInstance(@"\\integration_tests");
+    }
 
 
-            Log("Integration test lasted " + _testStopwatch.Elapsed);
-            KillInstance(@"\\integration_tests");
-        }
+    private TestService CreateDirectoryServiceController()
+    {
+        const string serviceName = "Abc.Zebus.DirectoryService";
+        var configFile = PathUtil.InCurrentNamespaceDirectory(@"Configurations\Directory-Local.config");
+        var buildFile = GetPathFromRepositoryBase(@"src\Abc.Zebus\Abc.Zebus.DirectoryService\Abc.Zebus.Directory.build");
 
 
-        private TestService CreateDirectoryServiceController()
-        {
-            const string serviceName = "Abc.Zebus.DirectoryService";
-            var configFile = PathUtil.InCurrentNamespaceDirectory(@"Configurations\Directory-Local.config");
-            var buildFile = GetPathFromRepositoryBase(@"src\Abc.Zebus\Abc.Zebus.DirectoryService\Abc.Zebus.Directory.build");
+        return new TestService(serviceName, configFile, buildFile) { RedirectOutput = false };
+    }
 
 
-            return new TestService(serviceName, configFile, buildFile) { RedirectOutput = false };
-        }
+    public MessageWaiter<TMessage> ListenForMessageMatchingCondition<TMessage>(Func<TMessage, bool> desiredCondition)
+        where TMessage : class, IMessage
+    {
+        return new MessageWaiter<TMessage>(_controlBus, desiredCondition);
+    }
 
 
-        public MessageWaiter<TMessage> ListenForMessageMatchingCondition<TMessage>(Func<TMessage, bool> desiredCondition) where TMessage : class, IMessage
-        {
-            return new MessageWaiter<TMessage>(_controlBus, desiredCondition);
-        }
+    protected static IBus CreateAndStartSenderBus()
+    {
+        return new BusFactory().WithConfiguration("tcp://localhost:129", "Local")
+                               .WithWaitForEndOfStreamAckTimeout(500.Milliseconds())
+                               .CreateAndStartBus();
+    }
 
 
-        protected static IBus CreateAndStartSenderBus()
-        {
-            return new BusFactory().WithConfiguration("tcp://localhost:129", "Local")
-                                   .WithWaitForEndOfStreamAckTimeout(500.Milliseconds())
-                                   .CreateAndStartBus();
-        }
+    protected static IBus CreateAndStartControlBus()
+    {
+        // Unobstrusive way of making the bus retry the register process
+        var directoryEndPoint = "tcp://localhost:129 tcp://localhost:129 tcp://localhost:129";
+        return new BusFactory().WithConfiguration(directoryEndPoint, "Local")
+                               .WithPeerId("Abc.ControlBus." + Guid.NewGuid().ToString().Substring(0, 6))
+                               .WithWaitForEndOfStreamAckTimeout(500.Milliseconds())
+                               .CreateAndStartBus();
+    }
 
 
-        protected static IBus CreateAndStartControlBus()
-        {
-            // Unobstrusive way of making the bus retry the register process
-            var directoryEndPoint = "tcp://localhost:129 tcp://localhost:129 tcp://localhost:129";
-            return new BusFactory().WithConfiguration(directoryEndPoint, "Local")
-                                   .WithPeerId("Abc.ControlBus." + Guid.NewGuid().ToString().Substring(0, 6))
-                                   .WithWaitForEndOfStreamAckTimeout(500.Milliseconds())
-                                   .CreateAndStartBus();
-        }
+    protected static void Log(string text)
+    {
+        Console.WriteLine("[{0:HH:mm:ss.fff}][IntegrationTestController] {1}", DateTime.Now, text);
+    }
+
+    protected void NinjaLog(string text)
+    {
+        Console.WriteLine(
+            $"""
+
+                   ___
+                  /___\_/
+                  |\_/|<\
+                  (`o`) `   __(\_            |\_    {DateTime.Now:HH:mm:ss.fff}
+                  \ ~ /_.-`` _|__)  ( ( ( ( /()/    {text}
+                 _/`-`  _.-``               `\|
+              .-`      (    .-.
+             (   .-     \  /   `-._
+              \  (\_    /\/        `-.__-()
+               `-|__)__/ /  /``-.   /_____8
+                     \__/  /     `-`
+                    />|   /
+                   /| J   L
+                   `` |   |
+                      L___J
+                       ( |
+                      .oO()
+             ______________________________________________________________________________
+
+             """
+        );
+    }
+
+    public class MessageWaiter<TMessage> : IDisposable
+        where TMessage : class, IMessage
+    {
+        private IDisposable _subscription;
+        private ManualResetEvent _conditionHappened = new(false);
 
 
-        protected static void Log(string text)
+        public MessageWaiter(IBus bus, Func<TMessage, bool> desiredMessageCondition)
         {
         {
-            Console.WriteLine("[{0:HH:mm:ss.fff}][IntegrationTestController] {1}", DateTime.Now, text);
+            _subscription = bus.Subscribe<TMessage>(msg =>
+            {
+                if (desiredMessageCondition(msg))
+                    _conditionHappened.Set();
+            });
         }
         }
 
 
-        protected void NinjaLog(string text)
+        public void Wait()
         {
         {
-            Console.WriteLine(@"
-      ___
-     /___\_/
-     |\_/|<\
-     (`o`) `   __(\_            |\_    " + DateTime.Now.ToString("HH:mm:ss.fff") + @"
-     \ ~ /_.-`` _|__)  ( ( ( ( /()/    " + text + @"
-    _/`-`  _.-``               `\|
- .-`      (    .-.
-(   .-     \  /   `-._
- \  (\_    /\/        `-.__-()
-  `-|__)__/ /  /``-.   /_____8
-        \__/  /     `-`
-       />|   /
-      /| J   L
-      `` |   |
-         L___J
-          ( |
-         .oO()
-______________________________________________________________________________
-");
+            if (!_conditionHappened.WaitOne(_controlBusActionsTimeout))
+                throw new TimeoutException("The desired condition was not met in the allotted time.");
         }
         }
 
 
-        public class MessageWaiter<TMessage> : IDisposable where TMessage : class, IMessage
+        public void Dispose()
         {
         {
-            private IDisposable _subscription;
-            private ManualResetEvent _conditionHappened = new ManualResetEvent(false);
-
-            public MessageWaiter(IBus bus, Func<TMessage, bool> desiredMessageCondition)
-            {
-                _subscription = bus.Subscribe<TMessage>(msg =>
-                {
-                    if (desiredMessageCondition(msg))
-                        _conditionHappened.Set();
-                });
-            }
-
-            public void Wait()
-            {
-                if (!_conditionHappened.WaitOne(_controlBusActionsTimeout))
-                    throw new TimeoutException("The desired condition was not met in the allotted time.");
-            }
-
-            public void Dispose()
-            {
-                _subscription.Dispose();
-            }
+            _subscription.Dispose();
         }
         }
+    }
 
 
-        public static string GetPathFromRepositoryBase(string relativeFilePath)
+    public static string GetPathFromRepositoryBase(string relativeFilePath)
+    {
+        var srcDirName = "\\src\\";
+        var currentDir = PathUtil.InBaseDirectory();
+        var position = 0;
+
+        for (int i = 1; i < 10; i++)
         {
         {
-            var srcDirName = "\\src\\";
-            var currentDir = PathUtil.InBaseDirectory();
-            var position = 0;
+            position = currentDir.IndexOf(srcDirName, position, StringComparison.Ordinal);
+            var srcDir = currentDir.Substring(0, position);
+            if (File.Exists(Path.Combine(srcDir, @".hgignore")))
+                return Path.Combine(srcDir, relativeFilePath);
 
 
-            for (int i = 1; i < 10; i++)
-            {
-                position = currentDir.IndexOf(srcDirName, position, StringComparison.Ordinal);
-                var srcDir = currentDir.Substring(0, position);
-                if (File.Exists(Path.Combine(srcDir, @".hgignore")))
-                    return Path.Combine(srcDir, relativeFilePath);
+            position += srcDirName.Length;
+        }
 
 
-                position += srcDirName.Length;
-            }
+        throw new Exception();
+    }
 
 
-            throw new Exception();
-        }
+    private static void KillInstance(string serviceFolder)
+    {
+        KillInstance(serviceFolder, Environment.MachineName);
+    }
 
 
-        private static void KillInstance(string serviceFolder)
-        {
-            KillInstance(serviceFolder, Environment.MachineName);
-        }
+    private static void KillInstance(string serviceFolder, string machineName)
+    {
+        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            throw new InvalidOperationException("Integration tests are only supported on Windows");
 
 
-        private static void KillInstance(string serviceFolder, string machineName)
+        try
         {
         {
-            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-                throw new InvalidOperationException("Integration tests are only supported on Windows");
+            var managementScope = new ManagementScope(@"\\" + machineName + @"\ROOT\CIMV2", new ConnectionOptions());
+            managementScope.Connect();
 
 
-            try
-            {
-                var managementScope = new ManagementScope(@"\\" + machineName + @"\ROOT\CIMV2", new ConnectionOptions());
-                managementScope.Connect();
-
-                var query = $@"SELECT Handle FROM Win32_Process WHERE Name = 'Abc.Zebus.Host.exe' AND ExecutablePath LIKE '%{serviceFolder}%'";
-                var searcher = new ManagementObjectSearcher(managementScope, new ObjectQuery(query));
+            var query = $@"SELECT Handle FROM Win32_Process WHERE Name = 'Abc.Zebus.Host.exe' AND ExecutablePath LIKE '%{serviceFolder}%'";
+            var searcher = new ManagementObjectSearcher(managementScope, new ObjectQuery(query));
 
 
-                var processes = searcher.Get().Cast<ManagementObject>().ToList();
+            var processes = searcher.Get().Cast<ManagementObject>().ToList();
 
 
-                for (int i = 0; i < processes.Count; i++)
+            for (int i = 0; i < processes.Count; i++)
+            {
+                var process = processes[i];
+                if (process != null)
                 {
                 {
-                    var process = processes[i];
-                    if (process != null)
-                    {
-                        process.InvokeMethod("Terminate", null);
-                        Console.WriteLine("Process #{0} on {1} - KILLED", i, machineName);
-                    }
+                    process.InvokeMethod("Terminate", null);
+                    Console.WriteLine("Process #{0} on {1} - KILLED", i, machineName);
                 }
                 }
             }
             }
-            catch (Exception ex)
-            {
-                Console.WriteLine("ERROR: Cannot kill process on machine {0}", machineName);
-                Console.WriteLine(ex);
-                Console.ReadLine();
-            }
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("ERROR: Cannot kill process on machine {0}", machineName);
+            Console.WriteLine(ex);
+            Console.ReadLine();
         }
         }
     }
     }
 }
 }

+ 134 - 135
src/Abc.Zebus.Testing/Integration/TestService.cs

@@ -5,162 +5,161 @@ using System.Threading;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace Abc.Zebus.Testing.Integration
+namespace Abc.Zebus.Testing.Integration;
+
+public class TestService : IDisposable
 {
 {
-    public class TestService : IDisposable
+    private Process? _process;
+    private TextWriter _outputWriter = Console.Out;
+    private TextWriter _errorWriter = Console.Error;
+    private string _serviceName;
+    private readonly string _configurationFile;
+    private readonly string _buildFile;
+    private Mutex? _stopMutex;
+    private bool _isMutexReleased;
+    private string _buildDirectory;
+    const string _hostFileName = "Abc.Zebus.Host.exe";
+    private const string _tempFolder = @"C:\Dev\integration_tests";
+
+    public bool RedirectOutput { get; set; }
+
+    public TestService(string serviceName, string configurationFile, string buildFile)
     {
     {
-        private Process? _process;
-        private TextWriter _outputWriter = Console.Out;
-        private TextWriter _errorWriter = Console.Error;
-        private string _serviceName;
-        private readonly string _configurationFile;
-        private readonly string _buildFile;
-        private Mutex? _stopMutex;
-        private bool _isMutexReleased;
-        private string _buildDirectory;
-        const string _hostFileName = "Abc.Zebus.Host.exe";
-        private const string _tempFolder = @"C:\Dev\integration_tests";
-
-        public bool RedirectOutput { get; set; }
-
-        public TestService(string serviceName, string configurationFile, string buildFile)
-        {
-            if (!System.IO.Directory.Exists(_tempFolder))
-                System.IO.Directory.CreateDirectory(_tempFolder);
-            _buildDirectory = Path.Combine(_tempFolder, Guid.NewGuid().ToString());
-            _serviceName = serviceName;
-            _configurationFile = configurationFile;
-            _buildFile = buildFile;
+        if (!System.IO.Directory.Exists(_tempFolder))
+            System.IO.Directory.CreateDirectory(_tempFolder);
+        _buildDirectory = Path.Combine(_tempFolder, Guid.NewGuid().ToString());
+        _serviceName = serviceName;
+        _configurationFile = configurationFile;
+        _buildFile = buildFile;
 
 
-            RedirectOutput = true;
+        RedirectOutput = true;
 
 
-            if(!File.Exists(configurationFile))
-                throw new ArgumentException("Unknown configuration file: " + configurationFile + ". Make sure that \"Copy to output directory\" is set.");
+        if(!File.Exists(configurationFile))
+            throw new ArgumentException("Unknown configuration file: " + configurationFile + ". Make sure that \"Copy to output directory\" is set.");
 
 
-            if(!File.Exists(buildFile))
-                throw new ArgumentException("Unknown build file: " + buildFile);
-        }
+        if(!File.Exists(buildFile))
+            throw new ArgumentException("Unknown build file: " + buildFile);
+    }
 
 
-        public void Build()
+    public void Build()
+    {
+        LogInfo("Building service in \"" + _buildDirectory + "\"");
+        System.IO.Directory.CreateDirectory(_buildDirectory);
+        var process = new Process
         {
         {
-            LogInfo("Building service in \"" + _buildDirectory + "\"");
-            System.IO.Directory.CreateDirectory(_buildDirectory);
-            var process = new Process
+            StartInfo = new ProcessStartInfo(IntegrationTestFixture.GetPathFromRepositoryBase(@"tools\nant\nant.exe"))
             {
             {
-                StartInfo = new ProcessStartInfo(IntegrationTestFixture.GetPathFromRepositoryBase(@"tools\nant\nant.exe"))
-                {
-                    UseShellExecute = false,
-                    RedirectStandardOutput = RedirectOutput,
-                    RedirectStandardInput = RedirectOutput,
-                    RedirectStandardError = RedirectOutput,
-                    CreateNoWindow = true,
-                    Arguments = "-buildfile:" + Path.GetFileName(_buildFile) + " build-only -D:build.dir=\"" + _buildDirectory + "\"",
-                    WorkingDirectory = Path.GetDirectoryName(_buildFile)!,
-                }
-            };
-
-            process.ErrorDataReceived += (sender, args) => LogError(args.Data);
-            process.OutputDataReceived += (sender, args) => LogInfo(args.Data);
-
-            process.Start();
-
-            if(RedirectOutput)
-                process.BeginOutputReadLine();
-
-            process.WaitForExit();
-            LogInfo("Build complete");
-
-            File.Copy(_configurationFile, Path.Combine(_buildDirectory, "Abc.Zebus.Host.exe.config"), true);
-            LogInfo("Config file copied");
-        }
-
-        public void Start()
-        {
-            LogInfo("Starting " + _serviceName);
+                UseShellExecute = false,
+                RedirectStandardOutput = RedirectOutput,
+                RedirectStandardInput = RedirectOutput,
+                RedirectStandardError = RedirectOutput,
+                CreateNoWindow = true,
+                Arguments = "-buildfile:" + Path.GetFileName(_buildFile) + " build-only -D:build.dir=\"" + _buildDirectory + "\"",
+                WorkingDirectory = Path.GetDirectoryName(_buildFile)!,
+            }
+        };
 
 
-            var mutexName = _serviceName + "." + Guid.NewGuid().ToString().Substring(0, 6);
-            CreateMutex(mutexName);
+        process.ErrorDataReceived += (sender, args) => LogError(args.Data);
+        process.OutputDataReceived += (sender, args) => LogInfo(args.Data);
 
 
-            _process = new Process
-            {
-                StartInfo = new ProcessStartInfo(Path.Combine(_buildDirectory, _hostFileName))
-                {
-                    WorkingDirectory = _buildDirectory,
-                    UseShellExecute = false,
-                    RedirectStandardOutput = RedirectOutput,
-                    RedirectStandardInput = RedirectOutput,
-                    RedirectStandardError = RedirectOutput,
-                    CreateNoWindow = true,
-                    Arguments = "/MutexName:" + mutexName
-                }
-            };
-
-            _process.ErrorDataReceived += (sender, args) => LogError(args.Data);
-            _process.OutputDataReceived += (sender, args) => LogInfo(args.Data);
-            _process.Start();
-
-            if (RedirectOutput)
-                _process.BeginOutputReadLine();
-        }
-
-        private void LogError(string? text)
-        {
-            _errorWriter.WriteLine("[{0:HH:mm:ss.fff}][{1}Manager] {2}", DateTime.Now, _serviceName, text);
-        }
+        process.Start();
 
 
-        private void LogInfo(string? text)
-        {
-            _outputWriter.WriteLine("[{0:HH:mm:ss.fff}][{1}Manager] {2}", DateTime.Now, _serviceName, text);
-        }
+        if(RedirectOutput)
+            process.BeginOutputReadLine();
 
 
-        public void Stop()
-        {
-            Stop(50.Seconds());
-        }
+        process.WaitForExit();
+        LogInfo("Build complete");
 
 
-        public void Stop(TimeSpan timeout)
-        {
-            LogInfo("Closing service");
-            ReleaseMutex();
+        File.Copy(_configurationFile, Path.Combine(_buildDirectory, "Abc.Zebus.Host.exe.config"), true);
+        LogInfo("Config file copied");
+    }
 
 
-            if (_process != null && !_process.WaitForExit((int)timeout.TotalMilliseconds))
-                Assert.Fail(_serviceName + " did not exit properly");
-        }
+    public void Start()
+    {
+        LogInfo("Starting " + _serviceName);
 
 
-        private void CreateMutex(string mutexName)
-        {
-            _stopMutex = new Mutex(true, mutexName);
-            _isMutexReleased = false;
-        }
+        var mutexName = _serviceName + "." + Guid.NewGuid().ToString().Substring(0, 6);
+        CreateMutex(mutexName);
 
 
-        private void ReleaseMutex()
+        _process = new Process
         {
         {
-            if (_stopMutex == null || _isMutexReleased)
-                return;
+            StartInfo = new ProcessStartInfo(Path.Combine(_buildDirectory, _hostFileName))
+            {
+                WorkingDirectory = _buildDirectory,
+                UseShellExecute = false,
+                RedirectStandardOutput = RedirectOutput,
+                RedirectStandardInput = RedirectOutput,
+                RedirectStandardError = RedirectOutput,
+                CreateNoWindow = true,
+                Arguments = "/MutexName:" + mutexName
+            }
+        };
+
+        _process.ErrorDataReceived += (sender, args) => LogError(args.Data);
+        _process.OutputDataReceived += (sender, args) => LogInfo(args.Data);
+        _process.Start();
+
+        if (RedirectOutput)
+            _process.BeginOutputReadLine();
+    }
+
+    private void LogError(string? text)
+    {
+        _errorWriter.WriteLine("[{0:HH:mm:ss.fff}][{1}Manager] {2}", DateTime.Now, _serviceName, text);
+    }
 
 
-            _stopMutex.ReleaseMutex();
-            _stopMutex.Dispose();
-            _isMutexReleased = true;
-        }
+    private void LogInfo(string? text)
+    {
+        _outputWriter.WriteLine("[{0:HH:mm:ss.fff}][{1}Manager] {2}", DateTime.Now, _serviceName, text);
+    }
 
 
-        public void Kill()
-        {
-            if (_process is null)
-                return;
+    public void Stop()
+    {
+        Stop(50.Seconds());
+    }
 
 
-            LogInfo("Killing service");
-            _process.Kill();
-        }
+    public void Stop(TimeSpan timeout)
+    {
+        LogInfo("Closing service");
+        ReleaseMutex();
 
 
-        public void Dispose()
-        {
-            LogInfo("Disposing service");
-            System.IO.Directory.Delete(_buildDirectory, true);
-        }
+        if (_process != null && !_process.WaitForExit((int)timeout.TotalMilliseconds))
+            Assert.Fail(_serviceName + " did not exit properly");
+    }
 
 
-        ~TestService()
-        {
-            ReleaseMutex();
-        }
+    private void CreateMutex(string mutexName)
+    {
+        _stopMutex = new Mutex(true, mutexName);
+        _isMutexReleased = false;
+    }
+
+    private void ReleaseMutex()
+    {
+        if (_stopMutex == null || _isMutexReleased)
+            return;
+
+        _stopMutex.ReleaseMutex();
+        _stopMutex.Dispose();
+        _isMutexReleased = true;
+    }
+
+    public void Kill()
+    {
+        if (_process is null)
+            return;
+
+        LogInfo("Killing service");
+        _process.Kill();
+    }
+
+    public void Dispose()
+    {
+        LogInfo("Disposing service");
+        System.IO.Directory.Delete(_buildDirectory, true);
+    }
+
+    ~TestService()
+    {
+        ReleaseMutex();
     }
     }
 }
 }

+ 122 - 123
src/Abc.Zebus.Testing/Measurements/Measure.cs

@@ -4,149 +4,148 @@ using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 
 
-namespace Abc.Zebus.Testing.Measurements
+namespace Abc.Zebus.Testing.Measurements;
+
+internal static class Measure
 {
 {
-    internal static class Measure
-    {
-        private const double _µsInOneSecond = 1000000;
-        private static readonly object _lock = new object();
-        private static readonly double _µsFrequency = _µsInOneSecond / Stopwatch.Frequency;
+    private const double _µsInOneSecond = 1000000;
+    private static readonly object _lock = new();
+    private static readonly double _µsFrequency = _µsInOneSecond / Stopwatch.Frequency;
 
 
-        public static void Execution(long iterations, Action action)
+    public static void Execution(long iterations, Action action)
+    {
+        Execution(conf =>
         {
         {
-            Execution(conf =>
-            {
-                conf.Action = i => action();
-                conf.Iteration = (int)iterations;
+            conf.Action = i => action();
+            conf.Iteration = (int)iterations;
 
 
-            });
-        }
+        });
+    }
 
 
-        public static void Execution(Action<MeasureConfiguration> configurationAction)
-        {
-            var configuration = new MeasureConfiguration();
-            configurationAction(configuration);
+    public static void Execution(Action<MeasureConfiguration> configurationAction)
+    {
+        var configuration = new MeasureConfiguration();
+        configurationAction(configuration);
 
 
-            if (configuration.Action == null)
-                throw new ArgumentException("Action should be specified");
+        if (configuration.Action == null)
+            throw new ArgumentException("Action should be specified");
 
 
-            Bench(configuration.Action, configuration.WarmUpIteration);
+        Bench(configuration.Action, configuration.WarmUpIteration);
 
 
-            var results = Bench(configuration.Action, configuration.Iteration);
+        var results = Bench(configuration.Action, configuration.Iteration);
 
 
-            PrintResults(configuration, results);
-        }
+        PrintResults(configuration, results);
+    }
 
 
-        private static BenchResults Bench(Action<long> action, long iterationCount)
+    private static BenchResults Bench(Action<long> action, long iterationCount)
+    {
+        GC.Collect();
+        var ticks = new List<long>((int)iterationCount);
+        var maxIteration = 0L;
+        var maxTickCount = 0L;
+
+        var stopwatch = new Stopwatch();
+        var g0Count = GC.CollectionCount(0);
+        var g1Count = GC.CollectionCount(1);
+        var g2Count = GC.CollectionCount(2);
+        stopwatch.Start();
+        for (long i = 0; i < iterationCount; i++)
         {
         {
-            GC.Collect();
-            var ticks = new List<long>((int)iterationCount);
-            var maxIteration = 0L;
-            var maxTickCount = 0L;
-
-            var stopwatch = new Stopwatch();
-            var g0Count = GC.CollectionCount(0);
-            var g1Count = GC.CollectionCount(1);
-            var g2Count = GC.CollectionCount(2);
-            stopwatch.Start();
-            for (long i = 0; i < iterationCount; i++)
-            {
-                var originalTickCount = stopwatch.ElapsedTicks;
-                action(i);
-                var tickCount = stopwatch.ElapsedTicks - originalTickCount;
-                ticks.Add(tickCount);
-                if (tickCount <= maxTickCount)
-                    continue;
-
-                maxIteration = i;
-                maxTickCount = tickCount;
-            }
-            stopwatch.Stop();
-            g0Count = GC.CollectionCount(0) - g0Count;
-            g1Count = GC.CollectionCount(1) - g1Count;
-            g2Count = GC.CollectionCount(2) - g2Count;
-
-            return new BenchResults
-                       {
-                           Elapsed = stopwatch.Elapsed,
-                           G0Count = g0Count,
-                           G1Count = g1Count,
-                           G2Count = g2Count,
-                           MaxIterationIndex = maxIteration,
-                           Ticks = ticks
-                       };
+            var originalTickCount = stopwatch.ElapsedTicks;
+            action(i);
+            var tickCount = stopwatch.ElapsedTicks - originalTickCount;
+            ticks.Add(tickCount);
+            if (tickCount <= maxTickCount)
+                continue;
+
+            maxIteration = i;
+            maxTickCount = tickCount;
         }
         }
+        stopwatch.Stop();
+        g0Count = GC.CollectionCount(0) - g0Count;
+        g1Count = GC.CollectionCount(1) - g1Count;
+        g2Count = GC.CollectionCount(2) - g2Count;
+
+        return new BenchResults
+        {
+            Elapsed = stopwatch.Elapsed,
+            G0Count = g0Count,
+            G1Count = g1Count,
+            G2Count = g2Count,
+            MaxIterationIndex = maxIteration,
+            Ticks = ticks
+        };
+    }
 
 
-        private static void PrintResults(MeasureConfiguration configuration, BenchResults results)
+    private static void PrintResults(MeasureConfiguration configuration, BenchResults results)
+    {
+        results.Ticks.Sort();
+        var min = results.Ticks.First();
+        var max = results.Ticks.Last();
+        var onePercentIndex = (int)Math.Floor((decimal)results.Ticks.Count * 1 / 100) + 1;
+        var fivePercentIndex = (int)Math.Floor((decimal)results.Ticks.Count * 5 / 100) + 1;
+        var medianIndex = (int)Math.Floor((decimal)results.Ticks.Count * 50 / 100) + 1;
+        var onePercentile = results.Ticks[results.Ticks.Count - onePercentIndex];
+        var fivePercentile = results.Ticks[results.Ticks.Count - fivePercentIndex];
+        var median = results.Ticks[results.Ticks.Count - medianIndex];
+
+        lock (_lock)
         {
         {
-            results.Ticks.Sort();
-            var min = results.Ticks.First();
-            var max = results.Ticks.Last();
-            var onePercentIndex = (int)Math.Floor((decimal)results.Ticks.Count * 1 / 100) + 1;
-            var fivePercentIndex = (int)Math.Floor((decimal)results.Ticks.Count * 5 / 100) + 1;
-            var medianIndex = (int)Math.Floor((decimal)results.Ticks.Count * 50 / 100) + 1;
-            var onePercentile = results.Ticks[results.Ticks.Count - onePercentIndex];
-            var fivePercentile = results.Ticks[results.Ticks.Count - fivePercentIndex];
-            var median = results.Ticks[results.Ticks.Count - medianIndex];
-
-            lock (_lock)
+            if (!string.IsNullOrEmpty(configuration.Name))
             {
             {
-                if (!string.IsNullOrEmpty(configuration.Name))
-                {
-                    Console.WriteLine();
-                    Console.WriteLine("[" + configuration.Name + "]");
-                }
-
-                Console.WriteLine("{0:N0} iterations in {1:N0} ms ({2:N1} iterations/sec)",
-                                  configuration.Iteration,
-                                  results.Elapsed.TotalMilliseconds,
-                                  configuration.Iteration / results.Elapsed.TotalSeconds);
-
-                Console.WriteLine("Latencies :");
-                Console.WriteLine("Min :          {0,10:### ### ##0}µs", min * _µsFrequency);
-                Console.WriteLine("Avg :          {0,10:### ### ##0}µs", (double)results.Elapsed.Ticks / configuration.Iteration / (TimeSpan.TicksPerMillisecond / 1000));
-                Console.WriteLine("Median :       {0,10:### ### ##0}µs", median * _µsFrequency);
-                Console.WriteLine("95 percentile : {0,10:### ### ##0}µs", fivePercentile * _µsFrequency);
-                Console.WriteLine("99 percentile : {0,10:### ### ##0}µs", onePercentile * _µsFrequency);
-                Console.WriteLine("Max :          {0,10:### ### ##0}µs (Iteration #{1})", max * _µsFrequency, results.MaxIterationIndex);
-                Console.WriteLine("G0 : {0}", results.G0Count);
-                Console.WriteLine("G1 : {0}", results.G1Count);
-                Console.WriteLine("G2 : {0}", results.G2Count);
+                Console.WriteLine();
+                Console.WriteLine("[" + configuration.Name + "]");
             }
             }
-        }
 
 
-        private class BenchResults
-        {
-            public TimeSpan Elapsed;
-            public int G0Count;
-            public int G1Count;
-            public int G2Count;
-            public long MaxIterationIndex;
-            public List<long> Ticks = default!;
+            Console.WriteLine("{0:N0} iterations in {1:N0} ms ({2:N1} iterations/sec)",
+                              configuration.Iteration,
+                              results.Elapsed.TotalMilliseconds,
+                              configuration.Iteration / results.Elapsed.TotalSeconds);
+
+            Console.WriteLine("Latencies :");
+            Console.WriteLine("Min :          {0,10:### ### ##0}µs", min * _µsFrequency);
+            Console.WriteLine("Avg :          {0,10:### ### ##0}µs", (double)results.Elapsed.Ticks / configuration.Iteration / (TimeSpan.TicksPerMillisecond / 1000));
+            Console.WriteLine("Median :       {0,10:### ### ##0}µs", median * _µsFrequency);
+            Console.WriteLine("95 percentile : {0,10:### ### ##0}µs", fivePercentile * _µsFrequency);
+            Console.WriteLine("99 percentile : {0,10:### ### ##0}µs", onePercentile * _µsFrequency);
+            Console.WriteLine("Max :          {0,10:### ### ##0}µs (Iteration #{1})", max * _µsFrequency, results.MaxIterationIndex);
+            Console.WriteLine("G0 : {0}", results.G0Count);
+            Console.WriteLine("G1 : {0}", results.G1Count);
+            Console.WriteLine("G2 : {0}", results.G2Count);
         }
         }
+    }
 
 
-        public static IDisposable Throughput(int count)
-        {
-            GC.Collect();
+    private class BenchResults
+    {
+        public TimeSpan Elapsed;
+        public int G0Count;
+        public int G1Count;
+        public int G2Count;
+        public long MaxIterationIndex;
+        public List<long> Ticks = default!;
+    }
 
 
-            var g0Count = GC.CollectionCount(0);
-            var g1Count = GC.CollectionCount(1);
-            var g2Count = GC.CollectionCount(2);
-            var stopwatch = Stopwatch.StartNew();
+    public static IDisposable Throughput(int count)
+    {
+        GC.Collect();
 
 
-            return new DisposableAction(() =>
-            {
-                stopwatch.Stop();
-                g0Count = GC.CollectionCount(0) - g0Count;
-                g1Count = GC.CollectionCount(1) - g1Count;
-                g2Count = GC.CollectionCount(2) - g2Count;
-
-                Console.WriteLine("Elapsed(ms):  {0,10:0.00}", stopwatch.Elapsed.TotalMilliseconds);
-                Console.WriteLine("FPS:          {0,10:0.00}", count / stopwatch.Elapsed.TotalSeconds);
-                Console.WriteLine("G0 : {0,6:0}", g0Count);
-                Console.WriteLine("G1 : {0,6:0}", g1Count);
-                Console.WriteLine("G2 : {0,6:0}", g2Count);
-            });
-        }
+        var g0Count = GC.CollectionCount(0);
+        var g1Count = GC.CollectionCount(1);
+        var g2Count = GC.CollectionCount(2);
+        var stopwatch = Stopwatch.StartNew();
+
+        return new DisposableAction(() =>
+        {
+            stopwatch.Stop();
+            g0Count = GC.CollectionCount(0) - g0Count;
+            g1Count = GC.CollectionCount(1) - g1Count;
+            g2Count = GC.CollectionCount(2) - g2Count;
+
+            Console.WriteLine("Elapsed(ms):  {0,10:0.00}", stopwatch.Elapsed.TotalMilliseconds);
+            Console.WriteLine("FPS:          {0,10:0.00}", count / stopwatch.Elapsed.TotalSeconds);
+            Console.WriteLine("G0 : {0,6:0}", g0Count);
+            Console.WriteLine("G1 : {0,6:0}", g1Count);
+            Console.WriteLine("G2 : {0,6:0}", g2Count);
+        });
     }
     }
 }
 }

+ 8 - 9
src/Abc.Zebus.Testing/Measurements/MeasureConfiguration.cs

@@ -1,14 +1,13 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Testing.Measurements
+namespace Abc.Zebus.Testing.Measurements;
+
+internal class MeasureConfiguration
 {
 {
-    internal class MeasureConfiguration
-    {
-        public int Iteration { get; set; }
-        public int WarmUpIteration { get; set; }
-        public string? Name { get; set; }
-        public Action<long>? Action { get; set; }
+    public int Iteration { get; set; }
+    public int WarmUpIteration { get; set; }
+    public string? Name { get; set; }
+    public Action<long>? Action { get; set; }
 
 
-        public int TotalIteration => Iteration + WarmUpIteration;
-    }
+    public int TotalIteration => Iteration + WarmUpIteration;
 }
 }

+ 60 - 61
src/Abc.Zebus.Testing/MessageSerializationTester.cs

@@ -10,89 +10,88 @@ using AutoFixture;
 using AutoFixture.Kernel;
 using AutoFixture.Kernel;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace Abc.Zebus.Testing
+namespace Abc.Zebus.Testing;
+
+public static class MessageSerializationTester
 {
 {
-    public static class MessageSerializationTester
+    private static readonly MethodInfo _createMethod = typeof(SpecimenFactory).GetMethod(nameof(SpecimenFactory.Create), new[] { typeof(ISpecimenBuilder) })!;
+    private static readonly MethodInfo _injectMethod = typeof(FixtureRegistrar).GetMethod(nameof(FixtureRegistrar.Inject))!;
+
+    public static void CheckSerializationForTypesInSameAssemblyAs<T>(params object[] prebuiltObjects)
     {
     {
-        private static readonly MethodInfo _createMethod = typeof(SpecimenFactory).GetMethod(nameof(SpecimenFactory.Create), new[] { typeof(ISpecimenBuilder) })!;
-        private static readonly MethodInfo _injectMethod = typeof(FixtureRegistrar).GetMethod(nameof(FixtureRegistrar.Inject))!;
+        var fixture = BuildFixture();
 
 
-        public static void CheckSerializationForTypesInSameAssemblyAs<T>(params object[] prebuiltObjects)
-        {
-            var fixture = BuildFixture();
+        var messageTypes = typeof(T).Assembly.GetTypes()
+                                    .Where(type => (type.Is<IMessage>() || type.GetInterfaces().Any(x => x.Name == "ISnapshot") || type.GetInterfaces().Any(x => x.Name == "IZmqMessage")))
+                                    .Where(type => !type.IsAbstract && !type.IsInterface && !type.IsGenericTypeDefinition);
 
 
-            var messageTypes = typeof(T).Assembly.GetTypes()
-                                        .Where(type => (type.Is<IMessage>() || type.GetInterfaces().Any(x => x.Name == "ISnapshot") || type.GetInterfaces().Any(x => x.Name == "IZmqMessage")))
-                                        .Where(type => !type.IsAbstract && !type.IsInterface && !type.IsGenericTypeDefinition);
+        var prebuildObjectsTypes = prebuiltObjects.Select(x => x.GetType()).ToList();
+        var typesToInstanciate = messageTypes.Where(msgType => !prebuildObjectsTypes.Contains(msgType)).ToList();
 
 
-            var prebuildObjectsTypes = prebuiltObjects.Select(x => x.GetType()).ToList();
-            var typesToInstanciate = messageTypes.Where(msgType => !prebuildObjectsTypes.Contains(msgType)).ToList();
+        foreach (var prebuiltObject in prebuiltObjects)
+        {
+            Inject(fixture, prebuiltObject);
+        }
 
 
-            foreach (var prebuiltObject in prebuiltObjects)
-            {
-                Inject(fixture, prebuiltObject);
-            }
+        foreach (var messageType in typesToInstanciate)
+            CheckSerializationForType(fixture, messageType);
 
 
-            foreach (var messageType in typesToInstanciate)
-                CheckSerializationForType(fixture, messageType);
+        foreach (var obj in prebuiltObjects)
+            CheckSerializationForType(fixture, obj.GetType(), obj);
 
 
-            foreach (var obj in prebuiltObjects)
-                CheckSerializationForType(fixture, obj.GetType(), obj);
+        var count = typesToInstanciate.Count + prebuiltObjects.Length;
+        Console.WriteLine("{0} message types tested", count);
+    }
 
 
-            var count = typesToInstanciate.Count + prebuiltObjects.Length;
-            Console.WriteLine("{0} message types tested", count);
-        }
+    public static void CheckSerializationFor<T>(T obj)
+        where T : notnull
+    {
+        var fixture = BuildFixture();
 
 
-        public static void CheckSerializationFor<T>(T obj)
-            where T : notnull
-        {
-            var fixture = BuildFixture();
+        Inject(fixture, obj);
+        CheckSerializationForType(fixture, typeof(T), obj);
 
 
-            Inject(fixture, obj);
-            CheckSerializationForType(fixture, typeof(T), obj);
+        Console.WriteLine("1 message type tested");
+    }
 
 
-            Console.WriteLine("1 message type tested");
-        }
+    private static Fixture BuildFixture()
+    {
+        var fixture = new Fixture();
 
 
-        private static Fixture BuildFixture()
-        {
-            var fixture = new Fixture();
+        fixture.Inject(new Uri(@"http://this.is.just.a.valid.url/"));
+        fixture.Inject('X');
 
 
-            fixture.Inject(new Uri(@"http://this.is.just.a.valid.url/"));
-            fixture.Inject('X');
+        return fixture;
+    }
 
 
-            return fixture;
-        }
+    private static void Inject(Fixture fixture, object obj)
+    {
+        var method = _injectMethod.MakeGenericMethod(obj.GetType());
+        method.Invoke(null, new[] { fixture, obj });
+    }
 
 
-        private static void Inject(Fixture fixture, object obj)
-        {
-            var method = _injectMethod.MakeGenericMethod(obj.GetType());
-            method.Invoke(null, new[] { fixture, obj });
-        }
+    private static void CheckSerializationForType(Fixture fixture, Type messageType, object? message = null)
+    {
+        Console.Write("Testing {0} ", messageType.Name);
 
 
-        private static void CheckSerializationForType(Fixture fixture, Type messageType, object? message = null)
+        if (message == null)
         {
         {
-            Console.Write("Testing {0} ", messageType.Name);
-
-            if (message == null)
-            {
-                var genericMethod = _createMethod.MakeGenericMethod(messageType);
-                message = genericMethod.Invoke(null, new object[] { fixture })!;
-            }
+            var genericMethod = _createMethod.MakeGenericMethod(messageType);
+            message = genericMethod.Invoke(null, new object[] { fixture })!;
+        }
 
 
-            Console.WriteLine("{{{0}}}", message);
+        Console.WriteLine("{{{0}}}", message);
 
 
-            var bytes = ProtoBufConvert.Serialize(message);
-            var messageCopy = ProtoBufConvert.Deserialize(messageType, bytes);
+        var bytes = ProtoBufConvert.Serialize(message);
+        var messageCopy = ProtoBufConvert.Deserialize(messageType, bytes);
 
 
-            messageCopy.ShouldNotBeNull();
+        messageCopy.ShouldNotBeNull();
 
 
-            var comparer = ComparisonExtensions.CreateComparer();
-            comparer.Config.MembersToIgnore = new List<string> { "Item" };
-            var result = comparer.Compare(message, messageCopy);
+        var comparer = ComparisonExtensions.CreateComparer();
+        comparer.Config.MembersToIgnore = new List<string> { "Item" };
+        var result = comparer.Compare(message, messageCopy);
 
 
-            if (!result.AreEqual)
-                Assert.Fail($"Messages are not equal after serialization{Environment.NewLine}Message type: {messageType.Name}{Environment.NewLine}{result.DifferencesString}");
-        }
+        if (!result.AreEqual)
+            Assert.Fail($"Messages are not equal after serialization{Environment.NewLine}Message type: {messageType.Name}{Environment.NewLine}{result.DifferencesString}");
     }
     }
 }
 }

+ 324 - 325
src/Abc.Zebus.Testing/TestBus.cs

@@ -8,447 +8,446 @@ using Abc.Zebus.Testing.Comparison;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Testing
+namespace Abc.Zebus.Testing;
+
+public class TestBus : IInternalBus
 {
 {
-    public class TestBus : IInternalBus
+    private readonly ConcurrentDictionary<HandlerKey, Func<IMessage, object?>> _handlers = new();
+    private readonly MessageComparer _messageComparer = new();
+    private readonly Dictionary<PeerId, List<IMessage>> _messagesByPeerId = new();
+    private readonly Dictionary<ICommand, Peer?> _peerByCommand = new();
+    private readonly List<IMessage> _messages = new();
+    private readonly HashSet<Subscription> _subscriptions = new();
+
+    public event Action? Starting;
+    public event Action? Started;
+    public event Action? Stopping;
+    public event Action? Stopped;
+
+    public IReadOnlyList<IMessage> Messages
     {
     {
-        private readonly ConcurrentDictionary<HandlerKey, Func<IMessage, object?>> _handlers = new();
-        private readonly MessageComparer _messageComparer = new();
-        private readonly Dictionary<PeerId, List<IMessage>> _messagesByPeerId = new();
-        private readonly Dictionary<ICommand, Peer?> _peerByCommand = new();
-        private readonly List<IMessage> _messages = new();
-        private readonly HashSet<Subscription> _subscriptions = new();
-
-        public event Action? Starting;
-        public event Action? Started;
-        public event Action? Stopping;
-        public event Action? Stopped;
-
-        public IReadOnlyList<IMessage> Messages
+        get
         {
         {
-            get
+            lock (_messages)
             {
             {
-                lock (_messages)
-                {
-                    return _messages.ToList();
-                }
+                return _messages.ToList();
             }
             }
         }
         }
+    }
 
 
-        public IReadOnlyList<ICommand> Commands
+    public IReadOnlyList<ICommand> Commands
+    {
+        get
         {
         {
-            get
+            lock (_messages)
             {
             {
-                lock (_messages)
-                {
-                    return _messages.OfType<ICommand>().ToList();
-                }
+                return _messages.OfType<ICommand>().ToList();
             }
             }
         }
         }
+    }
 
 
-        public IReadOnlyList<IEvent> Events
+    public IReadOnlyList<IEvent> Events
+    {
+        get
         {
         {
-            get
+            lock (_messages)
             {
             {
-                lock (_messages)
-                {
-                    return _messages.OfType<IEvent>().ToList();
-                }
+                return _messages.OfType<IEvent>().ToList();
             }
             }
         }
         }
+    }
 
 
-        public int MessageCount
+    public int MessageCount
+    {
+        get
         {
         {
-            get
+            lock (_messages)
             {
             {
-                lock (_messages)
-                {
-                    return _messages.Count;
-                }
+                return _messages.Count;
             }
             }
         }
         }
+    }
 
 
-        public HashSet<Subscription> Subscriptions
+    public HashSet<Subscription> Subscriptions
+    {
+        get
         {
         {
-            get
+            lock (_subscriptions)
             {
             {
-                lock (_subscriptions)
-                {
-                    return new HashSet<Subscription>(_subscriptions);
-                }
+                return new HashSet<Subscription>(_subscriptions);
             }
             }
         }
         }
+    }
 
 
-        public int LastReplyCode { get; set; }
-        public string? LastReplyMessage { get; set; }
-        public object? LastReplyResponse { get; set; }
-        public IHandlerExecutor HandlerExecutor { get; set; } = new DefaultHandlerExecutor();
-        public IMessageSerializer MessageSerializer { get; set; } = new MessageSerializer();
-        public bool IsStarted { get; private set; }
-        public bool IsStopped { get; private set; }
-        public PeerId PeerId { get; private set; }
-        public string Environment { get; private set; } = string.Empty;
-        public bool IsRunning { get; private set; }
-
-        public void Publish(IEvent message)
-        {
-            Publish(message, null);
-        }
+    public int LastReplyCode { get; set; }
+    public string? LastReplyMessage { get; set; }
+    public object? LastReplyResponse { get; set; }
+    public IHandlerExecutor HandlerExecutor { get; set; } = new DefaultHandlerExecutor();
+    public IMessageSerializer MessageSerializer { get; set; } = new MessageSerializer();
+    public bool IsStarted { get; private set; }
+    public bool IsStopped { get; private set; }
+    public PeerId PeerId { get; private set; }
+    public string Environment { get; private set; } = string.Empty;
+    public bool IsRunning { get; private set; }
+
+    public void Publish(IEvent message)
+    {
+        Publish(message, null);
+    }
+
+    public void Publish(IEvent message, PeerId targetPeer)
+    {
+        Publish(message, (PeerId?)targetPeer);
+    }
 
 
-        public void Publish(IEvent message, PeerId targetPeer)
+    private void Publish(IEvent message, PeerId? targetPeer)
+    {
+        if (MessageSerializer.TryClone(message, out var clone))
+            message = (IEvent)clone;
+
+        lock (_messages)
         {
         {
-            Publish(message, (PeerId?)targetPeer);
+            _messages.Add(message);
+            if (targetPeer != null)
+                _messagesByPeerId.GetValueOrAdd(targetPeer.Value, p => new List<IMessage>()).Add(message);
         }
         }
 
 
-        private void Publish(IEvent message, PeerId? targetPeer)
-        {
-            if (MessageSerializer.TryClone(message, out var clone))
-                message = (IEvent)clone;
+        if (_handlers.TryGetValue(new HandlerKey(message.GetType(), default), out var handler))
+            handler.Invoke(message);
+    }
 
 
-            lock (_messages)
-            {
-                _messages.Add(message);
-                if (targetPeer != null)
-                    _messagesByPeerId.GetValueOrAdd(targetPeer.Value, p => new List<IMessage>()).Add(message);
-            }
+    public Task<CommandResult> Send(ICommand message)
+    {
+        return Send(message, null);
+    }
 
 
-            if (_handlers.TryGetValue(new HandlerKey(message.GetType(), default), out var handler))
-                handler.Invoke(message);
-        }
+    public Task<CommandResult> Send(ICommand message, Peer? peer)
+    {
+        if (MessageSerializer.TryClone(message, out var clone))
+            message = (ICommand)clone;
 
 
-        public Task<CommandResult> Send(ICommand message)
+        lock (_messages)
         {
         {
-            return Send(message, null);
+            _messages.Add(message);
+            if (peer != null)
+                _messagesByPeerId.GetValueOrAdd(peer.Id, p => new List<IMessage>()).Add(message);
+
+            _peerByCommand[message] = peer;
         }
         }
 
 
-        public Task<CommandResult> Send(ICommand message, Peer? peer)
-        {
-            if (MessageSerializer.TryClone(message, out var clone))
-                message = (ICommand)clone;
+        Func<IMessage, object?>? handler;
 
 
-            lock (_messages)
-            {
-                _messages.Add(message);
-                if (peer != null)
-                    _messagesByPeerId.GetValueOrAdd(peer.Id, p => new List<IMessage>()).Add(message);
+        if (peer != null)
+            _handlers.TryGetValue(new HandlerKey(message.GetType(), peer.Id), out handler);
+        else
+            handler = null;
 
 
-                _peerByCommand[message] = peer;
-            }
+        // TODO why do we fall back in all cases?
+        if (handler == null)
+            _handlers.TryGetValue(new HandlerKey(message.GetType(), default), out handler);
 
 
-            Func<IMessage, object?>? handler;
+        return HandlerExecutor.Execute(message, handler);
+    }
 
 
-            if (peer != null)
-                _handlers.TryGetValue(new HandlerKey(message.GetType(), peer.Id), out handler);
-            else
-                handler = null;
+    public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
+    {
+        request.MarkAsSubmitted(0, IsRunning);
 
 
-            // TODO why do we fall back in all cases?
-            if (handler == null)
-                _handlers.TryGetValue(new HandlerKey(message.GetType(), default), out handler);
+        if (request.Batch != null)
+            await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
 
 
-            return HandlerExecutor.Execute(message, handler);
-        }
+        await AddSubscriptionsAsync(request).ConfigureAwait(false);
 
 
-        public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
-        {
-            request.MarkAsSubmitted(0, IsRunning);
+        return new DisposableAction(() => RemoveSubscriptions(request));
+    }
 
 
-            if (request.Batch != null)
-                await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
+    public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
+    {
+        request.MarkAsSubmitted(0, IsRunning);
 
 
-            await AddSubscriptionsAsync(request).ConfigureAwait(false);
+        if (request.Batch != null)
+            await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
 
 
-            return new DisposableAction(() => RemoveSubscriptions(request));
-        }
+        var handlerKeys = request.Subscriptions.Select(x => new HandlerKey(x.MessageTypeId.GetMessageType()!, default)).ToList();
 
 
-        public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
+        foreach (var handlerKey in handlerKeys)
         {
         {
-            request.MarkAsSubmitted(0, IsRunning);
-
-            if (request.Batch != null)
-                await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
-
-            var handlerKeys = request.Subscriptions.Select(x => new HandlerKey(x.MessageTypeId.GetMessageType()!, default)).ToList();
-
-            foreach (var handlerKey in handlerKeys)
+            _handlers[handlerKey] = x =>
             {
             {
-                _handlers[handlerKey] = x =>
-                {
-                    handler(x);
-                    return null;
-                };
-            }
+                handler(x);
+                return null;
+            };
+        }
 
 
-            await AddSubscriptionsAsync(request).ConfigureAwait(false);
+        await AddSubscriptionsAsync(request).ConfigureAwait(false);
 
 
-            return new DisposableAction(() =>
-            {
-                RemoveSubscriptions(request);
-                _handlers.RemoveRange(handlerKeys);
-            });
-        }
+        return new DisposableAction(() =>
+        {
+            RemoveSubscriptions(request);
+            _handlers.RemoveRange(handlerKeys);
+        });
+    }
 
 
-        private async Task AddSubscriptionsAsync(SubscriptionRequest request)
+    private async Task AddSubscriptionsAsync(SubscriptionRequest request)
+    {
+        if (request.Batch != null)
         {
         {
-            if (request.Batch != null)
+            var batchSubscriptions = request.Batch.TryConsumeBatchSubscriptions();
+            if (batchSubscriptions != null)
             {
             {
-                var batchSubscriptions = request.Batch.TryConsumeBatchSubscriptions();
-                if (batchSubscriptions != null)
-                {
-                    AddSubscriptions(batchSubscriptions);
-                    request.Batch.NotifyRegistrationCompleted(null);
-                }
-                else
-                {
-                    await request.Batch.WhenRegistrationCompletedAsync().ConfigureAwait(false);
-                }
+                AddSubscriptions(batchSubscriptions);
+                request.Batch.NotifyRegistrationCompleted(null);
             }
             }
             else
             else
             {
             {
-                AddSubscriptions(request.Subscriptions);
-            }
-
-            void AddSubscriptions(IEnumerable<Subscription> subscriptions)
-            {
-                lock (_subscriptions)
-                {
-                    _subscriptions.AddRange(subscriptions);
-                }
+                await request.Batch.WhenRegistrationCompletedAsync().ConfigureAwait(false);
             }
             }
         }
         }
+        else
+        {
+            AddSubscriptions(request.Subscriptions);
+        }
 
 
-        private void RemoveSubscriptions(SubscriptionRequest request)
+        void AddSubscriptions(IEnumerable<Subscription> subscriptions)
         {
         {
             lock (_subscriptions)
             lock (_subscriptions)
             {
             {
-                _subscriptions.RemoveRange(request.Subscriptions);
+                _subscriptions.AddRange(subscriptions);
             }
             }
         }
         }
+    }
 
 
-        public void Reply(int errorCode)
+    private void RemoveSubscriptions(SubscriptionRequest request)
+    {
+        lock (_subscriptions)
         {
         {
-            LastReplyCode = errorCode;
-            LastReplyMessage = null;
+            _subscriptions.RemoveRange(request.Subscriptions);
         }
         }
+    }
 
 
-        public void Reply(int errorCode, string? message)
-        {
-            LastReplyCode = errorCode;
-            LastReplyMessage = message;
-        }
+    public void Reply(int errorCode)
+    {
+        LastReplyCode = errorCode;
+        LastReplyMessage = null;
+    }
 
 
-        public void Reply(IMessage? response)
-        {
-            LastReplyResponse = response;
-        }
+    public void Reply(int errorCode, string? message)
+    {
+        LastReplyCode = errorCode;
+        LastReplyMessage = message;
+    }
 
 
-        public void Configure(PeerId peerId, string environment = "Test")
-        {
-            PeerId = peerId;
-            Environment = environment;
-        }
+    public void Reply(IMessage? response)
+    {
+        LastReplyResponse = response;
+    }
 
 
-        public void Start()
-        {
-            Starting?.Invoke();
-            IsStarted = true;
-            IsRunning = true;
-            Started?.Invoke();
-        }
+    public void Configure(PeerId peerId, string environment = "Test")
+    {
+        PeerId = peerId;
+        Environment = environment;
+    }
 
 
-        public void Stop()
-        {
-            Stopping?.Invoke();
-            IsStopped = true;
-            IsRunning = false;
-            Stopped?.Invoke();
-        }
+    public void Start()
+    {
+        Starting?.Invoke();
+        IsStarted = true;
+        IsRunning = true;
+        Started?.Invoke();
+    }
 
 
-        public void AddHandler<TMessage>(Func<TMessage, object?> handler)
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = x => handler((TMessage)x);
-        }
+    public void Stop()
+    {
+        Stopping?.Invoke();
+        IsStopped = true;
+        IsRunning = false;
+        Stopped?.Invoke();
+    }
 
 
-        public void AddSuccessfulHandler<TMessage>()
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = x => true;
-        }
+    public void AddHandler<TMessage>(Func<TMessage, object?> handler)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = x => handler((TMessage)x);
+    }
 
 
-        public void AddHandlerForPeer<TMessage>(PeerId peerId, Func<TMessage, object?> handler)
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), peerId)] = x => handler((TMessage)x);
-        }
+    public void AddSuccessfulHandler<TMessage>()
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = x => true;
+    }
 
 
-        public void AddHandlerForPeer<TMessage>(PeerId peerId, Action<TMessage> handler)
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), peerId)] = x =>
-            {
-                handler((TMessage)x);
-                return null;
-            };
-        }
+    public void AddHandlerForPeer<TMessage>(PeerId peerId, Func<TMessage, object?> handler)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), peerId)] = x => handler((TMessage)x);
+    }
 
 
-        public void AddHandler<TMessage>(Action<TMessage> handler)
-            where TMessage : IMessage
+    public void AddHandlerForPeer<TMessage>(PeerId peerId, Action<TMessage> handler)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), peerId)] = x =>
         {
         {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = x =>
-            {
-                handler((TMessage)x);
-                return null;
-            };
-        }
+            handler((TMessage)x);
+            return null;
+        };
+    }
 
 
-        public void AddHandlerThatThrowsDomainException<TMessage>(DomainException ex)
-            where TMessage : IMessage
+    public void AddHandler<TMessage>(Action<TMessage> handler)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = x =>
         {
         {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw ex;
-        }
+            handler((TMessage)x);
+            return null;
+        };
+    }
 
 
-        public void AddHandlerThatThrowsMessageProcessingException<TMessage>(MessageProcessingException ex)
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw ex;
-        }
+    public void AddHandlerThatThrowsDomainException<TMessage>(DomainException ex)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw ex;
+    }
 
 
-        public void AddHandlerThatThrows<TMessage>(Exception? ex = null)
-            where TMessage : IMessage
-        {
-            _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw (ex ?? new Exception());
-        }
+    public void AddHandlerThatThrowsMessageProcessingException<TMessage>(MessageProcessingException ex)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw ex;
+    }
 
 
-        public void Expect(IEnumerable<IMessage> expectedMessages)
-        {
-            _messageComparer.CheckExpectations(Messages, expectedMessages, false);
-        }
+    public void AddHandlerThatThrows<TMessage>(Exception? ex = null)
+        where TMessage : IMessage
+    {
+        _handlers[new HandlerKey(typeof(TMessage), default)] = _ => throw (ex ?? new Exception());
+    }
 
 
-        public void Expect(params IMessage[] expectedMessages)
-        {
-            _messageComparer.CheckExpectations(Messages, expectedMessages, false);
-        }
+    public void Expect(IEnumerable<IMessage> expectedMessages)
+    {
+        _messageComparer.CheckExpectations(Messages, expectedMessages, false);
+    }
 
 
-        public void ExpectExactly(params IMessage[] expectedMessages)
-        {
-            _messageComparer.CheckExpectations(Messages, expectedMessages, true);
-        }
+    public void Expect(params IMessage[] expectedMessages)
+    {
+        _messageComparer.CheckExpectations(Messages, expectedMessages, false);
+    }
 
 
-        public void ExpectExactly(PeerId peerId, params IMessage[] expectedMessages)
-        {
-            var messages = _messagesByPeerId.GetValueOrDefault(peerId, p => new List<IMessage>());
-            _messageComparer.CheckExpectations(messages, expectedMessages, true);
-        }
+    public void ExpectExactly(params IMessage[] expectedMessages)
+    {
+        _messageComparer.CheckExpectations(Messages, expectedMessages, true);
+    }
 
 
-        public void ExpectNothing()
-        {
-            _messageComparer.CheckExpectations(Messages, new List<object>(), true);
-        }
+    public void ExpectExactly(PeerId peerId, params IMessage[] expectedMessages)
+    {
+        var messages = _messagesByPeerId.GetValueOrDefault(peerId, p => new List<IMessage>());
+        _messageComparer.CheckExpectations(messages, expectedMessages, true);
+    }
 
 
-        public IList<PeerId> GetContactedPeerIds()
-        {
-            return _messagesByPeerId.Keys.ToList();
-        }
+    public void ExpectNothing()
+    {
+        _messageComparer.CheckExpectations(Messages, new List<object>(), true);
+    }
 
 
-        public Peer? GetRecipientPeer(ICommand command)
+    public IList<PeerId> GetContactedPeerIds()
+    {
+        return _messagesByPeerId.Keys.ToList();
+    }
+
+    public Peer? GetRecipientPeer(ICommand command)
+    {
+        return _peerByCommand[command];
+    }
+
+    /// <summary>
+    /// Executes handler synchronously (simulate locally handled messages).
+    /// </summary>
+    public class DefaultHandlerExecutor : IHandlerExecutor
+    {
+        public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
         {
         {
-            return _peerByCommand[command];
+            var taskCompletionSource = new TaskCompletionSource<CommandResult>();
+            try
+            {
+                var result = handler?.Invoke(command);
+                taskCompletionSource.SetResult(new CommandResult(0, null, result));
+            }
+            catch (MessageProcessingException ex)
+            {
+                taskCompletionSource.SetResult(new CommandResult(ex.ErrorCode, ex.Message, null));
+            }
+            catch (Exception)
+            {
+                taskCompletionSource.SetResult(new CommandResult(1, null, null));
+            }
+
+            return taskCompletionSource.Task;
         }
         }
+    }
 
 
-        /// <summary>
-        /// Executes handler synchronously (simulate locally handled messages).
-        /// </summary>
-        public class DefaultHandlerExecutor : IHandlerExecutor
+    /// <summary>
+    /// Executes handler asynchronously (simulate remotely handled messages or messages handled in
+    /// a different dispatch queue).
+    /// </summary>
+    public class AsyncHandlerExecutor : IHandlerExecutor
+    {
+        public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
         {
         {
-            public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
+            return Task.Run(() =>
             {
             {
-                var taskCompletionSource = new TaskCompletionSource<CommandResult>();
                 try
                 try
                 {
                 {
-                    var result = handler?.Invoke(command);
-                    taskCompletionSource.SetResult(new CommandResult(0, null, result));
+                    var response = handler?.Invoke(command);
+                    return new CommandResult(0, null, response);
                 }
                 }
                 catch (MessageProcessingException ex)
                 catch (MessageProcessingException ex)
                 {
                 {
-                    taskCompletionSource.SetResult(new CommandResult(ex.ErrorCode, ex.Message, null));
+                    return new CommandResult(ex.ErrorCode, ex.Message, null);
                 }
                 }
-                catch (Exception)
+                catch (Exception ex)
                 {
                 {
-                    taskCompletionSource.SetResult(new CommandResult(1, null, null));
+                    return new CommandResult(1, ex.Message, null);
                 }
                 }
-
-                return taskCompletionSource.Task;
-            }
-        }
-
-        /// <summary>
-        /// Executes handler asynchronously (simulate remotely handled messages or messages handled in
-        /// a different dispatch queue).
-        /// </summary>
-        public class AsyncHandlerExecutor : IHandlerExecutor
-        {
-            public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
-            {
-                return Task.Run(() =>
-                {
-                    try
-                    {
-                        var response = handler?.Invoke(command);
-                        return new CommandResult(0, null, response);
-                    }
-                    catch (MessageProcessingException ex)
-                    {
-                        return new CommandResult(ex.ErrorCode, ex.Message, null);
-                    }
-                    catch (Exception ex)
-                    {
-                        return new CommandResult(1, ex.Message, null);
-                    }
-                });
-            }
+            });
         }
         }
+    }
 
 
-        /// <summary>
-        /// Never executes handler (simulate down peer).
-        /// </summary>
-        public class DoNotReplyHandlerExecutor : IHandlerExecutor
+    /// <summary>
+    /// Never executes handler (simulate down peer).
+    /// </summary>
+    public class DoNotReplyHandlerExecutor : IHandlerExecutor
+    {
+        public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
         {
         {
-            public Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler)
-            {
-                return new TaskCompletionSource<CommandResult>().Task;
-            }
+            return new TaskCompletionSource<CommandResult>().Task;
         }
         }
+    }
 
 
-        public interface IHandlerExecutor
-        {
-            Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler);
-        }
+    public interface IHandlerExecutor
+    {
+        Task<CommandResult> Execute(ICommand command, Func<IMessage, object?>? handler);
+    }
 
 
-        private class HandlerKey : Tuple<Type, PeerId>
+    private class HandlerKey : Tuple<Type, PeerId>
+    {
+        public HandlerKey(Type type, PeerId peerId)
+            : base(type, peerId)
         {
         {
-            public HandlerKey(Type type, PeerId peerId)
-                : base(type, peerId)
-            {
-            }
         }
         }
+    }
 
 
-        public IDisposable InjectSubscription(Subscription subscription)
-            => this.Subscribe(subscription);
+    public IDisposable InjectSubscription(Subscription subscription)
+        => this.Subscribe(subscription);
 
 
-        public void Dispose()
-        {
-            Stop();
-        }
+    public void Dispose()
+    {
+        Stop();
+    }
 
 
-        public void ClearMessages()
+    public void ClearMessages()
+    {
+        lock (_messages)
         {
         {
-            lock (_messages)
-            {
-                _messages.Clear();
-                _peerByCommand.Clear();
-                _messagesByPeerId.Clear();
-            }
+            _messages.Clear();
+            _peerByCommand.Clear();
+            _messagesByPeerId.Clear();
         }
         }
     }
     }
 }
 }

+ 100 - 101
src/Abc.Zebus.Testing/TestExtensions.cs

@@ -13,107 +13,106 @@ using Abc.Zebus.Transport;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using Moq;
 using Moq;
 
 
-namespace Abc.Zebus.Testing
+namespace Abc.Zebus.Testing;
+
+public static class TestExtensions
 {
 {
-    public static class TestExtensions
-    {
-        public static TransportMessage ToTransportMessage(this IMessage message, Peer? sender = null, bool? wasPersisted = null)
-        {
-            sender ??= new Peer(new PeerId("Abc.Testing.Peer"), "tcp://abctest:159");
-
-            var serializer = new MessageSerializer();
-            var content = serializer.Serialize(message);
-
-            return new TransportMessage(message.TypeId(), content, sender) { WasPersisted = wasPersisted };
-        }
-
-        public static TransportMessage ToPersistTransportMessage(this TransportMessage transportMessage, params PeerId[] peerIds)
-        {
-            return transportMessage.ConvertToPersistTransportMessage(peerIds.ToList());
-        }
-
-        public static IMessage? ToMessage(this TransportMessage transportMessage)
-        {
-            var serializer = new MessageSerializer();
-            return serializer.ToMessage(transportMessage);
-        }
-
-        public static TransportMessage ToReplayedTransportMessage(this TransportMessage message, Guid replayId)
-        {
-            return new MessageReplayed(replayId, message).ToTransportMessage();
-        }
-
-        public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, IEnumerable<Subscription> subscriptions)
-        {
-            return new PeerDescriptor(peer.Id, peer.EndPoint, isPersistent, peer.IsUp, peer.IsResponding, SystemDateTime.UtcNow, subscriptions.ToArray());
-        }
-
-        public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params MessageTypeId[] messageTypeIds)
-        {
-            return peer.ToPeerDescriptor(isPersistent, messageTypeIds.Select(x => new Subscription(x)));
-        }
-
-        public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params Type[] messageTypes)
-        {
-            return peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(MessageUtil.GetTypeId(x))));
-        }
-
-        public static PeerDescriptor ToPeerDescriptorWithRoundedTime(this Peer peer, bool isPersistent, params Type[] messageTypes)
-        {
-            var descriptor = peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(MessageUtil.GetTypeId(x))));
-            descriptor.TimestampUtc = SystemDateTime.UtcNow.RoundToMillisecond();
-            return descriptor;
-        }
-
-        public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params string[] messageTypes)
-        {
-            return peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(new MessageTypeId(x))));
-        }
-
-        public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent)
-        {
-            return peer.ToPeerDescriptor(isPersistent, Enumerable.Empty<Subscription>());
-        }
-
-        public static IBus CreateAndStartInMemoryBus(this BusFactory busFactory)
-        {
-            return busFactory.CreateAndStartInMemoryBus(new TestPeerDirectory(), new TestTransport());
-        }
-
-        public static IBus CreateAndStartInMemoryBus(this BusFactory busFactory, TestPeerDirectory directory, ITransport transport)
-        {
-            return busFactory.WithConfiguration("in-memory-bus", "Memory")
-                             .ConfigureContainer(cfg =>
-                             {
-                                 cfg.ForSingletonOf<IPeerDirectory>().Use(directory);
-                                 cfg.ForSingletonOf<ITransport>().Use(transport);
-                             }).CreateAndStartBus();
-        }
-
-        public static IMessageHandlerInvocation ToInvocation(this IMessage message, MessageContext context)
-        {
-            var invocationMock = new Mock<IMessageHandlerInvocation>();
-            invocationMock.SetupGet(x => x.Context).Returns(context);
-            invocationMock.SetupGet(x => x.Messages).Returns(new List<IMessage> { message });
-            invocationMock.Setup(x => x.SetupForInvocation()).Returns(() => MessageContext.SetCurrent(context));
-            invocationMock.Setup(x => x.SetupForInvocation(It.IsAny<object>())).Returns(() => MessageContext.SetCurrent(context));
-
-            return invocationMock.Object;
-        }
-
-        public static IMessageHandlerInvocation ToInvocation(this IMessage message)
-        {
-            return ToInvocation(message, MessageContext.CreateTest("u.name"));
-        }
-
-        public static IEnumerable<Subscription> OfMessageType<T>(this IEnumerable<Subscription> subscriptions) where T : IMessage
-        {
-            return subscriptions.Where(x => x.MessageTypeId == MessageUtil.TypeId<T>());
-        }
-
-        public static Peer GetPeer(this ZmqTransport transport)
-        {
-            return new Peer(transport.PeerId, transport.InboundEndPoint);
-        }
+    public static TransportMessage ToTransportMessage(this IMessage message, Peer? sender = null, bool? wasPersisted = null)
+    {
+        sender ??= new Peer(new PeerId("Abc.Testing.Peer"), "tcp://abctest:159");
+
+        var serializer = new MessageSerializer();
+        var content = serializer.Serialize(message);
+
+        return new TransportMessage(message.TypeId(), content, sender) { WasPersisted = wasPersisted };
+    }
+
+    public static TransportMessage ToPersistTransportMessage(this TransportMessage transportMessage, params PeerId[] peerIds)
+    {
+        return transportMessage.ConvertToPersistTransportMessage(peerIds.ToList());
+    }
+
+    public static IMessage? ToMessage(this TransportMessage transportMessage)
+    {
+        var serializer = new MessageSerializer();
+        return serializer.ToMessage(transportMessage);
+    }
+
+    public static TransportMessage ToReplayedTransportMessage(this TransportMessage message, Guid replayId)
+    {
+        return new MessageReplayed(replayId, message).ToTransportMessage();
+    }
+
+    public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, IEnumerable<Subscription> subscriptions)
+    {
+        return new PeerDescriptor(peer.Id, peer.EndPoint, isPersistent, peer.IsUp, peer.IsResponding, SystemDateTime.UtcNow, subscriptions.ToArray());
+    }
+
+    public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params MessageTypeId[] messageTypeIds)
+    {
+        return peer.ToPeerDescriptor(isPersistent, messageTypeIds.Select(x => new Subscription(x)));
+    }
+
+    public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params Type[] messageTypes)
+    {
+        return peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(MessageUtil.GetTypeId(x))));
+    }
+
+    public static PeerDescriptor ToPeerDescriptorWithRoundedTime(this Peer peer, bool isPersistent, params Type[] messageTypes)
+    {
+        var descriptor = peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(MessageUtil.GetTypeId(x))));
+        descriptor.TimestampUtc = SystemDateTime.UtcNow.RoundToMillisecond();
+        return descriptor;
+    }
+
+    public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent, params string[] messageTypes)
+    {
+        return peer.ToPeerDescriptor(isPersistent, messageTypes.Select(x => new Subscription(new MessageTypeId(x))));
+    }
+
+    public static PeerDescriptor ToPeerDescriptor(this Peer peer, bool isPersistent)
+    {
+        return peer.ToPeerDescriptor(isPersistent, Enumerable.Empty<Subscription>());
+    }
+
+    public static IBus CreateAndStartInMemoryBus(this BusFactory busFactory)
+    {
+        return busFactory.CreateAndStartInMemoryBus(new TestPeerDirectory(), new TestTransport());
+    }
+
+    public static IBus CreateAndStartInMemoryBus(this BusFactory busFactory, TestPeerDirectory directory, ITransport transport)
+    {
+        return busFactory.WithConfiguration("in-memory-bus", "Memory")
+                         .ConfigureContainer(cfg =>
+                         {
+                             cfg.ForSingletonOf<IPeerDirectory>().Use(directory);
+                             cfg.ForSingletonOf<ITransport>().Use(transport);
+                         }).CreateAndStartBus();
+    }
+
+    public static IMessageHandlerInvocation ToInvocation(this IMessage message, MessageContext context)
+    {
+        var invocationMock = new Mock<IMessageHandlerInvocation>();
+        invocationMock.SetupGet(x => x.Context).Returns(context);
+        invocationMock.SetupGet(x => x.Messages).Returns(new List<IMessage> { message });
+        invocationMock.Setup(x => x.SetupForInvocation()).Returns(() => MessageContext.SetCurrent(context));
+        invocationMock.Setup(x => x.SetupForInvocation(It.IsAny<object>())).Returns(() => MessageContext.SetCurrent(context));
+
+        return invocationMock.Object;
+    }
+
+    public static IMessageHandlerInvocation ToInvocation(this IMessage message)
+    {
+        return ToInvocation(message, MessageContext.CreateTest("u.name"));
+    }
+
+    public static IEnumerable<Subscription> OfMessageType<T>(this IEnumerable<Subscription> subscriptions) where T : IMessage
+    {
+        return subscriptions.Where(x => x.MessageTypeId == MessageUtil.TypeId<T>());
+    }
+
+    public static Peer GetPeer(this ZmqTransport transport)
+    {
+        return new Peer(transport.PeerId, transport.InboundEndPoint);
     }
     }
 }
 }

+ 103 - 105
src/Abc.Zebus.Testing/Transport/TestTransport.cs

@@ -1,6 +1,5 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Serialization;
 using Abc.Zebus.Serialization;
@@ -9,131 +8,130 @@ using Abc.Zebus.Testing.Extensions;
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace Abc.Zebus.Testing.Transport
+namespace Abc.Zebus.Testing.Transport;
+
+public class TestTransport : ITransport
 {
 {
-    public class TestTransport : ITransport
-    {
-        private readonly List<TransportMessageSent> _messages = new List<TransportMessageSent>();
-        private readonly List<UpdatedPeer> _updatedPeers = new List<UpdatedPeer>();
-        private readonly List<TransportMessage> _ackedMessages = new List<TransportMessage>();
-        private readonly MessageSerializer _messageSerializer = new MessageSerializer();
+    private readonly List<TransportMessageSent> _messages = new();
+    private readonly List<UpdatedPeer> _updatedPeers = new();
+    private readonly List<TransportMessage> _ackedMessages = new();
+    private readonly MessageSerializer _messageSerializer = new();
 
 
-        public TestTransport(Peer peer, string environment)
-        {
-            InboundEndPoint = peer.EndPoint;
-            Configure(peer.Id, environment);
-            MessagesSent = new List<IMessage>();
-        }
+    public TestTransport(Peer peer, string environment)
+    {
+        InboundEndPoint = peer.EndPoint;
+        Configure(peer.Id, environment);
+        MessagesSent = new List<IMessage>();
+    }
 
 
-        public TestTransport(string inboundEndPoint = "tcp://in-memory-test:1234")
-        {
-            InboundEndPoint = inboundEndPoint;
-            MessagesSent = new List<IMessage>();
-        }
+    public TestTransport(string inboundEndPoint = "tcp://in-memory-test:1234")
+    {
+        InboundEndPoint = inboundEndPoint;
+        MessagesSent = new List<IMessage>();
+    }
 
 
-        public event Action<TransportMessage> MessageReceived = delegate { };
-        public event Action Started = delegate { };
-        public event Action Registered = delegate { };
-
-        public PeerId PeerId { get; private set; }
-        public string InboundEndPoint { get; private set; }
-        public int PendingSendCount { get; set; }
-        public IEnumerable<UpdatedPeer> UpdatedPeers => _updatedPeers;
-        public bool IsStopped { get; private set; }
-        public bool IsStarted { get; private set; }
-        public bool IsConfigured { get; private set; }
-        public List<IMessage> MessagesSent { get; private set; }
-        public bool IsRegistered { get; private set; }
-
-        public void Configure(PeerId peerId, string environment)
-        {
-            PeerId = peerId;
-            IsConfigured = true;
-        }
+    public event Action<TransportMessage> MessageReceived = delegate { };
+    public event Action Started = delegate { };
+    public event Action Registered = delegate { };
+
+    public PeerId PeerId { get; private set; }
+    public string InboundEndPoint { get; private set; }
+    public int PendingSendCount { get; set; }
+    public IEnumerable<UpdatedPeer> UpdatedPeers => _updatedPeers;
+    public bool IsStopped { get; private set; }
+    public bool IsStarted { get; private set; }
+    public bool IsConfigured { get; private set; }
+    public List<IMessage> MessagesSent { get; private set; }
+    public bool IsRegistered { get; private set; }
+
+    public void Configure(PeerId peerId, string environment)
+    {
+        PeerId = peerId;
+        IsConfigured = true;
+    }
 
 
-        public void OnPeerUpdated(PeerId peerId, PeerUpdateAction peerUpdateAction)
-        {
-            _updatedPeers.Add(new UpdatedPeer(peerId, peerUpdateAction));
-        }
+    public void OnPeerUpdated(PeerId peerId, PeerUpdateAction peerUpdateAction)
+    {
+        _updatedPeers.Add(new UpdatedPeer(peerId, peerUpdateAction));
+    }
 
 
-        public void OnRegistered()
-        {
-            Registered();
-            IsRegistered = true;
-        }
+    public void OnRegistered()
+    {
+        Registered();
+        IsRegistered = true;
+    }
 
 
-        public void Start()
-        {
-            Started();
-            IsStarted = true;
-        }
+    public void Start()
+    {
+        Started();
+        IsStarted = true;
+    }
 
 
-        public void Stop()
-        {
-            IsStopped = true;
-        }
+    public void Stop()
+    {
+        IsStopped = true;
+    }
 
 
-        public void Send(TransportMessage message, IEnumerable<Peer> peers)
-        {
-            Send(message, peers, new SendContext());
-        }
+    public void Send(TransportMessage message, IEnumerable<Peer> peers)
+    {
+        Send(message, peers, new SendContext());
+    }
 
 
-        public void Send(TransportMessage message, IEnumerable<Peer> peers, SendContext context)
-        {
-            var peerList = peers.ToList();
-            if (peerList.Any() || context.PersistencePeer != null)
-                _messages.Add(new TransportMessageSent(message, peerList, context));
+    public void Send(TransportMessage message, IEnumerable<Peer> peers, SendContext context)
+    {
+        var peerList = peers.ToList();
+        if (peerList.Any() || context.PersistencePeer != null)
+            _messages.Add(new TransportMessageSent(message, peerList, context));
 
 
-            var deserializedMessage = _messageSerializer.Deserialize(message.MessageTypeId, message.Content!);
-            if (deserializedMessage != null)
-                MessagesSent.Add(deserializedMessage);
-        }
+        var deserializedMessage = _messageSerializer.Deserialize(message.MessageTypeId, message.Content!);
+        if (deserializedMessage != null)
+            MessagesSent.Add(deserializedMessage);
+    }
 
 
-        public void AckMessage(TransportMessage transportMessage)
-        {
-            _ackedMessages.Add(transportMessage);
-        }
+    public void AckMessage(TransportMessage transportMessage)
+    {
+        _ackedMessages.Add(transportMessage);
+    }
 
 
-        public TransportMessage CreateInfrastructureTransportMessage(MessageTypeId messageTypeId)
-        {
-            return new TransportMessage(messageTypeId, default, PeerId, InboundEndPoint);
-        }
+    public TransportMessage CreateInfrastructureTransportMessage(MessageTypeId messageTypeId)
+    {
+        return new TransportMessage(messageTypeId, default, PeerId, InboundEndPoint);
+    }
 
 
-        public void RaiseMessageReceived(TransportMessage transportMessage)
-        {
-            MessageReceived(transportMessage);
-        }
+    public void RaiseMessageReceived(TransportMessage transportMessage)
+    {
+        MessageReceived(transportMessage);
+    }
 
 
-        public IList<TransportMessageSent> Messages => _messages;
+    public IList<TransportMessageSent> Messages => _messages;
 
 
-        public IList<TransportMessage> AckedMessages => _ackedMessages;
+    public IList<TransportMessage> AckedMessages => _ackedMessages;
 
 
-        public void ExpectExactly(params TransportMessageSent[] expectedMessages)
-        {
-            var comparer = new MessageComparer();
-            comparer.CheckExpectations(Messages, expectedMessages, true);
-        }
+    public void ExpectExactly(params TransportMessageSent[] expectedMessages)
+    {
+        var comparer = new MessageComparer();
+        comparer.CheckExpectations(Messages, expectedMessages, true);
+    }
 
 
-        public void ExpectNothing()
-        {
-            NUnitExtensions.ShouldBeEmpty(Messages, "Messages not empty. Content:" + Environment.NewLine + string.Join(Environment.NewLine, Messages.Select(msg => msg.TransportMessage.MessageTypeId.GetMessageType()?.Name)));
-        }
+    public void ExpectNothing()
+    {
+        NUnitExtensions.ShouldBeEmpty(Messages, "Messages not empty. Content:" + Environment.NewLine + string.Join(Environment.NewLine, Messages.Select(msg => msg.TransportMessage.MessageTypeId.GetMessageType()?.Name)));
+    }
 
 
-        public void Expect(params TransportMessageSent[] expectedMessages)
-        {
-            var comparer = new MessageComparer();
-            comparer.CheckExpectations(Messages, expectedMessages, false);
-        }
+    public void Expect(params TransportMessageSent[] expectedMessages)
+    {
+        var comparer = new MessageComparer();
+        comparer.CheckExpectations(Messages, expectedMessages, false);
+    }
 
 
-        public void ExpectNot(params TransportMessageSent[] notExpectedMessages)
+    public void ExpectNot(params TransportMessageSent[] notExpectedMessages)
+    {
+        var comparer = ComparisonExtensions.CreateComparer();
+        foreach (var notExpectedMessage in notExpectedMessages)
         {
         {
-            var comparer = ComparisonExtensions.CreateComparer();
-            foreach (var notExpectedMessage in notExpectedMessages)
-            {
-                var matchingMessage = Messages.FirstOrDefault(x => comparer.Compare(notExpectedMessage, x).AreEqual);
-                if (matchingMessage != null)
-                    Assert.Fail("Found message matching " + notExpectedMessage.TransportMessage.MessageTypeId.GetMessageType()?.Name);
-            }
+            var matchingMessage = Messages.FirstOrDefault(x => comparer.Compare(notExpectedMessage, x).AreEqual);
+            if (matchingMessage != null)
+                Assert.Fail("Found message matching " + notExpectedMessage.TransportMessage.MessageTypeId.GetMessageType()?.Name);
         }
         }
     }
     }
 }
 }

+ 49 - 51
src/Abc.Zebus.Testing/Transport/TransportMessageSent.cs

@@ -1,58 +1,56 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Testing.Transport
+namespace Abc.Zebus.Testing.Transport;
+
+public class TransportMessageSent
 {
 {
-    public class TransportMessageSent
+    public readonly TransportMessage TransportMessage;
+    public readonly List<Peer> Targets = new();
+    public readonly SendContext Context;
+
+    public TransportMessageSent(TransportMessage transportMessage)
+        : this(transportMessage, Enumerable.Empty<Peer>(), new SendContext())
+    {
+    }
+
+    public TransportMessageSent(TransportMessage transportMessage, params Peer[] peers)
+        : this(transportMessage, peers, new SendContext())
+    {
+    }
+
+    public TransportMessageSent(TransportMessage transportMessage, Peer peer, bool wasPersistent = false)
+        : this(transportMessage)
+    {
+        To(peer, wasPersistent);
+    }
+
+    public TransportMessageSent(TransportMessage transportMessage, IEnumerable<Peer> targets, SendContext context)
     {
     {
-        public readonly TransportMessage TransportMessage;
-        public readonly List<Peer> Targets = new List<Peer>();
-        public readonly SendContext Context;
-
-        public TransportMessageSent(TransportMessage transportMessage)
-            : this(transportMessage, Enumerable.Empty<Peer>(), new SendContext())
-        {
-        }
-
-        public TransportMessageSent(TransportMessage transportMessage, params Peer[] peers)
-            : this(transportMessage, peers, new SendContext())
-        {
-        }
-
-        public TransportMessageSent(TransportMessage transportMessage, Peer peer, bool wasPersistent = false)
-            : this(transportMessage)
-        {
-            To(peer, wasPersistent);
-        }
-
-        public TransportMessageSent(TransportMessage transportMessage, IEnumerable<Peer> targets, SendContext context)
-        {
-            TransportMessage = transportMessage;
-            Targets.AddRange(targets);
-            Context = context;
-        }
-
-        public TransportMessageSent To(Peer peer, bool wasPersistent)
-        {
-            Targets.Add(peer);
-            if (wasPersistent)
-                Context.PersistentPeerIds.Add(peer.Id);
-
-            return this;
-        }
-
-        public TransportMessageSent ToPersistence(Peer persistencePeer)
-        {
-            Context.PersistencePeer = persistencePeer;
-            return this;
-        }
-
-        public TransportMessageSent AddPersistentPeer(Peer peer)
-        {
+        TransportMessage = transportMessage;
+        Targets.AddRange(targets);
+        Context = context;
+    }
+
+    public TransportMessageSent To(Peer peer, bool wasPersistent)
+    {
+        Targets.Add(peer);
+        if (wasPersistent)
             Context.PersistentPeerIds.Add(peer.Id);
             Context.PersistentPeerIds.Add(peer.Id);
-            return this;
-        }
+
+        return this;
+    }
+
+    public TransportMessageSent ToPersistence(Peer persistencePeer)
+    {
+        Context.PersistencePeer = persistencePeer;
+        return this;
+    }
+
+    public TransportMessageSent AddPersistentPeer(Peer peer)
+    {
+        Context.PersistentPeerIds.Add(peer.Id);
+        return this;
     }
     }
-}
+}

+ 32 - 33
src/Abc.Zebus.Testing/Transport/UpdatedPeer.cs

@@ -1,45 +1,44 @@
 using System;
 using System;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Directory;
 
 
-namespace Abc.Zebus.Testing.Transport
+namespace Abc.Zebus.Testing.Transport;
+
+public class UpdatedPeer : IEquatable<UpdatedPeer>
 {
 {
-    public class UpdatedPeer : IEquatable<UpdatedPeer>
-    {
-        public readonly PeerId PeerId;
-        public readonly PeerUpdateAction UpdateAction;
+    public readonly PeerId PeerId;
+    public readonly PeerUpdateAction UpdateAction;
 
 
-        public UpdatedPeer(PeerId peerId, PeerUpdateAction updateAction)
-        {
-            PeerId = peerId;
-            UpdateAction = updateAction;
-        }
+    public UpdatedPeer(PeerId peerId, PeerUpdateAction updateAction)
+    {
+        PeerId = peerId;
+        UpdateAction = updateAction;
+    }
 
 
-        public bool Equals(UpdatedPeer? other)
-        {
-            if (ReferenceEquals(null, other))
-                return false;
-            if (ReferenceEquals(this, other))
-                return true;
-            return PeerId.Equals(other.PeerId) && UpdateAction == other.UpdateAction;
-        }
+    public bool Equals(UpdatedPeer? other)
+    {
+        if (ReferenceEquals(null, other))
+            return false;
+        if (ReferenceEquals(this, other))
+            return true;
+        return PeerId.Equals(other.PeerId) && UpdateAction == other.UpdateAction;
+    }
 
 
-        public override bool Equals(object? obj)
-        {
-            if (ReferenceEquals(null, obj))
-                return false;
-            if (ReferenceEquals(this, obj))
-                return true;
-            if (obj.GetType() != this.GetType())
-                return false;
-            return Equals((UpdatedPeer)obj);
-        }
+    public override bool Equals(object? obj)
+    {
+        if (ReferenceEquals(null, obj))
+            return false;
+        if (ReferenceEquals(this, obj))
+            return true;
+        if (obj.GetType() != this.GetType())
+            return false;
+        return Equals((UpdatedPeer)obj);
+    }
 
 
-        public override int GetHashCode()
+    public override int GetHashCode()
+    {
+        unchecked
         {
         {
-            unchecked
-            {
-                return (PeerId.GetHashCode() * 397) ^ (int)UpdateAction;
-            }
+            return (PeerId.GetHashCode() * 397) ^ (int)UpdateAction;
         }
         }
     }
     }
 }
 }

+ 33 - 34
src/Abc.Zebus.Testing/UnitTesting/MoqExtensions.cs

@@ -6,46 +6,45 @@ using Abc.Zebus.Util.Extensions;
 using Moq;
 using Moq;
 using Moq.Language.Flow;
 using Moq.Language.Flow;
 
 
-namespace Abc.Zebus.Testing.UnitTesting
+namespace Abc.Zebus.Testing.UnitTesting;
+
+internal static class MoqExtensions
 {
 {
-    internal static class MoqExtensions
+    public static void CaptureEnumerable<TMock, TArg, TArg0>(this Mock<TMock> mock, TArg0 arg0, Expression<Action<TMock, TArg0, IEnumerable<TArg>>> action, ICollection<TArg> target)
+        where TMock : class
     {
     {
-        public static void CaptureEnumerable<TMock, TArg, TArg0>(this Mock<TMock> mock, TArg0 arg0, Expression<Action<TMock, TArg0, IEnumerable<TArg>>> action, ICollection<TArg> target)
-            where TMock : class
-        {
-            var mockParam = Expression.Parameter(typeof(TMock));
-            var arg0Param = Expression.Constant(arg0);
-            var enumerableParam = Expression.Call(typeof(It).GetMethod(nameof(It.IsAny))!.MakeGenericMethod(typeof(IEnumerable<TArg>))!);
-            var methodCall = (MethodCallExpression)action.Body;
-            var methodExpression = Expression.Call(mockParam, methodCall.Method, arg0Param, enumerableParam);
-            var expression = Expression.Lambda<Action<TMock>>(methodExpression, mockParam);
+        var mockParam = Expression.Parameter(typeof(TMock));
+        var arg0Param = Expression.Constant(arg0);
+        var enumerableParam = Expression.Call(typeof(It).GetMethod(nameof(It.IsAny))!.MakeGenericMethod(typeof(IEnumerable<TArg>))!);
+        var methodCall = (MethodCallExpression)action.Body;
+        var methodExpression = Expression.Call(mockParam, methodCall.Method, arg0Param, enumerableParam);
+        var expression = Expression.Lambda<Action<TMock>>(methodExpression, mockParam);
 
 
-            mock.Setup(expression).Callback<TArg0, IEnumerable<TArg>>((x, items) => items.ForEach(target.Add));
-        }
+        mock.Setup(expression).Callback<TArg0, IEnumerable<TArg>>((x, items) => items.ForEach(target.Add));
+    }
 
 
-        public static void CaptureEnumerable<TMock, TArg, TArg0>(this Mock<TMock> mock, TArg0 arg0, Expression<Func<TMock, TArg0, IEnumerable<TArg>, Task>> actionAsync, ICollection<TArg> target)
-            where TMock : class
-        {
-            var mockParam = Expression.Parameter(typeof(TMock));
-            var arg0Param = Expression.Constant(arg0);
-            var enumerableParam = Expression.Call(typeof(It).GetMethod(nameof(It.IsAny))!.MakeGenericMethod(typeof(IEnumerable<TArg>))!);
-            var methodCall = (MethodCallExpression)actionAsync.Body;
-            var methodExpression = Expression.Call(mockParam, methodCall.Method, arg0Param, enumerableParam);
-            var expression = Expression.Lambda<Func<TMock, Task>>(methodExpression, mockParam);
+    public static void CaptureEnumerable<TMock, TArg, TArg0>(this Mock<TMock> mock, TArg0 arg0, Expression<Func<TMock, TArg0, IEnumerable<TArg>, Task>> actionAsync, ICollection<TArg> target)
+        where TMock : class
+    {
+        var mockParam = Expression.Parameter(typeof(TMock));
+        var arg0Param = Expression.Constant(arg0);
+        var enumerableParam = Expression.Call(typeof(It).GetMethod(nameof(It.IsAny))!.MakeGenericMethod(typeof(IEnumerable<TArg>))!);
+        var methodCall = (MethodCallExpression)actionAsync.Body;
+        var methodExpression = Expression.Call(mockParam, methodCall.Method, arg0Param, enumerableParam);
+        var expression = Expression.Lambda<Func<TMock, Task>>(methodExpression, mockParam);
 
 
-            mock.Setup(expression).Callback<TArg0, IEnumerable<TArg>>((x, items) => items.ForEach(target.Add)).Returns(Task.CompletedTask);
-        }
+        mock.Setup(expression).Callback<TArg0, IEnumerable<TArg>>((x, items) => items.ForEach(target.Add)).Returns(Task.CompletedTask);
+    }
 
 
-        public static ICallbackResult InSequence<TMock>(this ISetup<TMock> setup, SetupSequence sequence)
-            where TMock : class
-        {
-            return setup.Callback(sequence.GetCallback(setup.ToString()));
-        }
+    public static ICallbackResult InSequence<TMock>(this ISetup<TMock> setup, SetupSequence sequence)
+        where TMock : class
+    {
+        return setup.Callback(sequence.GetCallback(setup.ToString()));
+    }
 
 
-        public static IReturnsThrows<TMock, TResult> InSequence<TMock, TResult>(this ISetup<TMock, TResult> setup, SetupSequence sequence)
-            where TMock : class
-        {
-            return setup.Callback(sequence.GetCallback(setup.ToString()));
-        }
+    public static IReturnsThrows<TMock, TResult> InSequence<TMock, TResult>(this ISetup<TMock, TResult> setup, SetupSequence sequence)
+        where TMock : class
+    {
+        return setup.Callback(sequence.GetCallback(setup.ToString()));
     }
     }
 }
 }

+ 31 - 32
src/Abc.Zebus.Testing/UnitTesting/SetupSequence.cs

@@ -2,41 +2,40 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace Abc.Zebus.Testing.UnitTesting
+namespace Abc.Zebus.Testing.UnitTesting;
+
+/// <summary>
+/// Usage:
+/// <code>
+/// var sequence = new SetupSequence();
+/// mock.Setup(x => x.Foo()).InSequence(sequence);
+/// mock.Setup(x => x.Bar()).InSequence(sequence);
+/// //...
+/// sequence.Verify();
+/// </code>
+/// </summary>
+internal class SetupSequence
 {
 {
-    /// <summary>
-    /// Usage:
-    /// <code>
-    /// var sequence = new SetupSequence();
-    /// mock.Setup(x => x.Foo()).InSequence(sequence);
-    /// mock.Setup(x => x.Bar()).InSequence(sequence);
-    /// //...
-    /// sequence.Verify();
-    /// </code>
-    /// </summary>
-    internal class SetupSequence
-    {
-        private readonly List<string> _errorMessages = new List<string>();
-        private int _expectedOrderSequence;
-        private int _order;
+    private readonly List<string> _errorMessages = new();
+    private int _expectedOrderSequence;
+    private int _order;
 
 
-        public Action GetCallback(string? expression = null)
+    public Action GetCallback(string? expression = null)
+    {
+        var expectedOrder = _expectedOrderSequence;
+        ++_expectedOrderSequence;
+        return () =>
         {
         {
-            var expectedOrder = _expectedOrderSequence;
-            ++_expectedOrderSequence;
-            return () =>
-            {
-                if (expectedOrder == _order)
-                    ++_order;
-                else if (expression != null)
-                    _errorMessages.Add("Expected sequence index " + expectedOrder + " but was " + _order + ": " + expression);
-            };
-        }
+            if (expectedOrder == _order)
+                ++_order;
+            else if (expression != null)
+                _errorMessages.Add("Expected sequence index " + expectedOrder + " but was " + _order + ": " + expression);
+        };
+    }
 
 
-        public void Verify()
-        {
-            var messageText = _errorMessages.Count == 0 ? "Sequence is not in order" : string.Join(Environment.NewLine, _errorMessages);
-            Assert.That(_order, Is.EqualTo(_expectedOrderSequence), messageText);
-        }
+    public void Verify()
+    {
+        var messageText = _errorMessages.Count == 0 ? "Sequence is not in order" : string.Join(Environment.NewLine, _errorMessages);
+        Assert.That(_order, Is.EqualTo(_expectedOrderSequence), messageText);
     }
     }
 }
 }

+ 17 - 18
src/Abc.Zebus.Testing/Wait.cs

@@ -4,30 +4,29 @@ using System.Threading;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using JetBrains.Annotations;
 using JetBrains.Annotations;
 
 
-namespace Abc.Zebus.Testing
+namespace Abc.Zebus.Testing;
+
+internal static class Wait
 {
 {
-    internal static class Wait
-    {
-        public static void Until([InstantHandle] Func<bool> exitCondition, int timeoutInSeconds)
-            => Until(exitCondition, timeoutInSeconds.Seconds(), () => string.Empty);
+    public static void Until([InstantHandle] Func<bool> exitCondition, int timeoutInSeconds)
+        => Until(exitCondition, timeoutInSeconds.Seconds(), () => string.Empty);
 
 
-        public static void Until([InstantHandle] Func<bool> exitCondition, TimeSpan timeout, string? message = null)
-            => Until(exitCondition, timeout, () => message ?? "Timed out");
+    public static void Until([InstantHandle] Func<bool> exitCondition, TimeSpan timeout, string? message = null)
+        => Until(exitCondition, timeout, () => message ?? "Timed out");
 
 
-        public static void Until([InstantHandle] Func<bool> exitCondition, TimeSpan timeout, Func<string?> message)
-        {
-            var sw = Stopwatch.StartNew();
+    public static void Until([InstantHandle] Func<bool> exitCondition, TimeSpan timeout, Func<string?> message)
+    {
+        var sw = Stopwatch.StartNew();
 
 
-            while (true)
-            {
-                if (exitCondition())
-                    break;
+        while (true)
+        {
+            if (exitCondition())
+                break;
 
 
-                if (sw.Elapsed > timeout)
-                    throw new TimeoutException(message?.Invoke() ?? "Timed out");
+            if (sw.Elapsed > timeout)
+                throw new TimeoutException(message?.Invoke() ?? "Timed out");
 
 
-                Thread.Sleep(10);
-            }
+            Thread.Sleep(10);
         }
         }
     }
     }
 }
 }

+ 52 - 53
src/Abc.Zebus/BusExtensions.cs

@@ -5,80 +5,79 @@ using System.Threading.Tasks;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+public static class BusExtensions
 {
 {
-    public static class BusExtensions
+    public static Task<CommandResult> SendWithoutLocalDispatch(this IBus bus, ICommand command)
     {
     {
-        public static Task<CommandResult> SendWithoutLocalDispatch(this IBus bus, ICommand command)
+        using (LocalDispatch.Disable())
         {
         {
-            using (LocalDispatch.Disable())
-            {
-                return bus.Send(command);
-            }
+            return bus.Send(command);
         }
         }
+    }
 
 
-        public static void PublishWithoutLocalDispatch(this IBus bus, IEvent @event)
+    public static void PublishWithoutLocalDispatch(this IBus bus, IEvent @event)
+    {
+        using (LocalDispatch.Disable())
         {
         {
-            using (LocalDispatch.Disable())
-            {
-                bus.Publish(@event);
-            }
+            bus.Publish(@event);
         }
         }
+    }
 
 
-        public static Task Send(this IBus bus, IEnumerable<ICommand> commands)
-        {
-            var sendTasks = commands.Select(bus.Send);
-            return Task.WhenAll(sendTasks);
-        }
+    public static Task Send(this IBus bus, IEnumerable<ICommand> commands)
+    {
+        var sendTasks = commands.Select(bus.Send);
+        return Task.WhenAll(sendTasks);
+    }
 
 
-        public static Task Send<T>(this IBus bus, IEnumerable<T> commands, Action<ICommand, int> onCommandExecuted)
-            where T : ICommand
-        {
-            var sendTasks = commands.Select(command => bus.Send(command).ContinueWith(task => onCommandExecuted(command, task.Result.ErrorCode), TaskContinuationOptions.ExecuteSynchronously));
+    public static Task Send<T>(this IBus bus, IEnumerable<T> commands, Action<ICommand, int> onCommandExecuted)
+        where T : ICommand
+    {
+        var sendTasks = commands.Select(command => bus.Send(command).ContinueWith(task => onCommandExecuted(command, task.Result.ErrorCode), TaskContinuationOptions.ExecuteSynchronously));
 
 
-            return Task.WhenAll(sendTasks);
-        }
+        return Task.WhenAll(sendTasks);
+    }
 
 
-        public static Task<bool> SendMany(this IBus bus, IEnumerable<ICommand> commands, Action<ICommand, CommandResult>? onCommandExecuted = null)
+    public static Task<bool> SendMany(this IBus bus, IEnumerable<ICommand> commands, Action<ICommand, CommandResult>? onCommandExecuted = null)
+    {
+        var sendTasks = commands.Select(command =>
         {
         {
-            var sendTasks = commands.Select(command =>
-            {
-                var sendTask = bus.Send(command);
-                if (onCommandExecuted != null)
-                    sendTask.ContinueWith(task => onCommandExecuted(command, task.Result));
+            var sendTask = bus.Send(command);
+            if (onCommandExecuted != null)
+                sendTask.ContinueWith(task => onCommandExecuted(command, task.Result));
 
 
-                return sendTask;
-            });
+            return sendTask;
+        });
 
 
-            return Task.WhenAll(sendTasks).ContinueWith(t => t.Result.All(x => x.IsSuccess));
-        }
+        return Task.WhenAll(sendTasks).ContinueWith(t => t.Result.All(x => x.IsSuccess));
+    }
 
 
-        public static Task<IDisposable> SubscribeAsync(this IBus bus, Subscription subscription)
-            => bus.SubscribeAsync(new SubscriptionRequest(subscription));
+    public static Task<IDisposable> SubscribeAsync(this IBus bus, Subscription subscription)
+        => bus.SubscribeAsync(new SubscriptionRequest(subscription));
 
 
-        public static Task<IDisposable> SubscribeAsync(this IBus bus, IEnumerable<Subscription> subscriptions)
-            => bus.SubscribeAsync(new SubscriptionRequest(subscriptions));
+    public static Task<IDisposable> SubscribeAsync(this IBus bus, IEnumerable<Subscription> subscriptions)
+        => bus.SubscribeAsync(new SubscriptionRequest(subscriptions));
 
 
-        public static Task<IDisposable> SubscribeAsync(this IBus bus, Subscription subscription, Action<IMessage> handler)
-            => bus.SubscribeAsync(new SubscriptionRequest(subscription), handler);
+    public static Task<IDisposable> SubscribeAsync(this IBus bus, Subscription subscription, Action<IMessage> handler)
+        => bus.SubscribeAsync(new SubscriptionRequest(subscription), handler);
 
 
-        public static Task<IDisposable> SubscribeAsync(this IBus bus, IEnumerable<Subscription> subscriptions, Action<IMessage> handler)
-            => bus.SubscribeAsync(new SubscriptionRequest(subscriptions), handler);
+    public static Task<IDisposable> SubscribeAsync(this IBus bus, IEnumerable<Subscription> subscriptions, Action<IMessage> handler)
+        => bus.SubscribeAsync(new SubscriptionRequest(subscriptions), handler);
 
 
-        public static IDisposable Subscribe(this IBus bus, Subscription subscription)
-            => SubscribeAsync(bus, subscription).WaitSync();
+    public static IDisposable Subscribe(this IBus bus, Subscription subscription)
+        => SubscribeAsync(bus, subscription).WaitSync();
 
 
-        public static IDisposable Subscribe(this IBus bus, IEnumerable<Subscription> subscriptions)
-            => SubscribeAsync(bus, subscriptions).WaitSync();
+    public static IDisposable Subscribe(this IBus bus, IEnumerable<Subscription> subscriptions)
+        => SubscribeAsync(bus, subscriptions).WaitSync();
 
 
-        public static IDisposable Subscribe<T>(this IBus bus, Action<T> handler)
-            where T : class, IMessage
-            => Subscribe(bus, Subscription.Any<T>(), msg => handler((T)msg));
+    public static IDisposable Subscribe<T>(this IBus bus, Action<T> handler)
+        where T : class, IMessage
+        => Subscribe(bus, Subscription.Any<T>(), msg => handler((T)msg));
 
 
-        public static IDisposable Subscribe(this IBus bus, Subscription subscription, Action<IMessage> handler)
-            => SubscribeAsync(bus, subscription, handler).WaitSync();
+    public static IDisposable Subscribe(this IBus bus, Subscription subscription, Action<IMessage> handler)
+        => SubscribeAsync(bus, subscription, handler).WaitSync();
 
 
-        public static IDisposable Subscribe(this IBus bus, IEnumerable<Subscription> subscriptions, Action<IMessage> handler)
-            => SubscribeAsync(bus, subscriptions, handler).WaitSync();
-    }
+    public static IDisposable Subscribe(this IBus bus, IEnumerable<Subscription> subscriptions, Action<IMessage> handler)
+        => SubscribeAsync(bus, subscriptions, handler).WaitSync();
 }
 }

+ 47 - 48
src/Abc.Zebus/CommandResult.cs

@@ -4,68 +4,67 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus
+namespace Abc.Zebus;
+
+/// <summary>
+/// Contains the result of a bus command.
+/// </summary>
+/// <remarks>
+/// <see cref="CommandResult"/> should probably not be instantiated by user code outside unit tests.
+/// </remarks>
+public class CommandResult
 {
 {
-    /// <summary>
-    /// Contains the result of a bus command.
-    /// </summary>
-    /// <remarks>
-    /// <see cref="CommandResult"/> should probably not be instantiated by user code outside unit tests.
-    /// </remarks>
-    public class CommandResult
+    public CommandResult(int errorCode, string? responseMessage, object? response)
     {
     {
-        public CommandResult(int errorCode, string? responseMessage, object? response)
-        {
-            ErrorCode = errorCode;
-            ResponseMessage = responseMessage;
-            Response = response;
-        }
+        ErrorCode = errorCode;
+        ResponseMessage = responseMessage;
+        Response = response;
+    }
 
 
-        public int ErrorCode { get; }
-        public string? ResponseMessage { get; }
-        public object? Response { get; }
+    public int ErrorCode { get; }
+    public string? ResponseMessage { get; }
+    public object? Response { get; }
 
 
-        public bool IsSuccess => ErrorCode == 0;
+    public bool IsSuccess => ErrorCode == 0;
 
 
-        public string GetErrorMessageFromEnum<T>(params object[] formatValues) where T : struct, IConvertible, IFormattable, IComparable
-        {
-            if (IsSuccess)
-                return string.Empty;
+    public string GetErrorMessageFromEnum<T>(params object[] formatValues) where T : struct, IConvertible, IFormattable, IComparable
+    {
+        if (IsSuccess)
+            return string.Empty;
 
 
-            var value = (T)Enum.Parse(typeof(T), ErrorCode.ToString());
+        var value = (T)Enum.Parse(typeof(T), ErrorCode.ToString());
 
 
-            return string.Format(((Enum)(object)value).GetAttributeDescription(), formatValues);
-        }
+        return string.Format(((Enum)(object)value).GetAttributeDescription(), formatValues);
+    }
 
 
-        public static CommandResult Success(object? response = null)
-            => new CommandResult(0, null, response);
+    public static CommandResult Success(object? response = null)
+        => new CommandResult(0, null, response);
 
 
-        public static CommandResult Error(int errorCode = 1, string? responseMessage = null)
-        {
-            if (errorCode == 0)
-                throw new ArgumentException("error code cannot be zero", nameof(errorCode));
+    public static CommandResult Error(int errorCode = 1, string? responseMessage = null)
+    {
+        if (errorCode == 0)
+            throw new ArgumentException("error code cannot be zero", nameof(errorCode));
 
 
-            return new CommandResult(errorCode, responseMessage, null);
-        }
+        return new CommandResult(errorCode, responseMessage, null);
+    }
 
 
-        internal static ErrorStatus GetErrorStatus(IEnumerable<Exception> exceptions)
-        {
-            return exceptions.FirstOrDefault() is MessageProcessingException ex
-                ? new ErrorStatus(ex.ErrorCode, ex.Message)
-                : ErrorStatus.UnknownError;
-        }
+    internal static ErrorStatus GetErrorStatus(IEnumerable<Exception> exceptions)
+    {
+        return exceptions.FirstOrDefault() is MessageProcessingException ex
+            ? new ErrorStatus(ex.ErrorCode, ex.Message)
+            : ErrorStatus.UnknownError;
+    }
 
 
-        public override string ToString()
-        {
-            var text = new StringBuilder(IsSuccess ? "Success" : $"Error, ErrorCode: {ErrorCode}");
+    public override string ToString()
+    {
+        var text = new StringBuilder(IsSuccess ? "Success" : $"Error, ErrorCode: {ErrorCode}");
 
 
-            if (ResponseMessage != null)
-                text.Append($", ResponseMessage: [{ResponseMessage}]");
+        if (ResponseMessage != null)
+            text.Append($", ResponseMessage: [{ResponseMessage}]");
 
 
-            if (Response != null)
-                text.Append($", Response: [{Response}]");
+        if (Response != null)
+            text.Append($", Response: [{Response}]");
 
 
-            return text.ToString();
-        }
+        return text.ToString();
     }
     }
 }
 }

+ 664 - 665
src/Abc.Zebus/Core/Bus.cs

@@ -17,912 +17,911 @@ using Abc.Zebus.Util.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class Bus : IInternalBus, IMessageDispatchFactory
 {
 {
-    public class Bus : IInternalBus, IMessageDispatchFactory
+    private static readonly BusMessageLogger _messageLogger = new(typeof(Bus));
+    private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(Bus));
+
+    private readonly ConcurrentDictionary<MessageId, TaskCompletionSource<CommandResult>> _messageIdToTaskCompletionSources = new();
+    private readonly UniqueTimestampProvider _deserializationFailureTimestampProvider = new();
+    private readonly Dictionary<Subscription, SubscriptionStatus> _subscriptions = new();
+    private readonly List<IMessageHandlerInvoker> _startupInvokers = new();
+    private readonly HashSet<MessageTypeId> _pendingUnsubscriptions = new();
+    private readonly ITransport _transport;
+    private readonly IPeerDirectory _directory;
+    private readonly IMessageSerializer _serializer;
+    private readonly IMessageDispatcher _messageDispatcher;
+    private readonly IMessageSendingStrategy _messageSendingStrategy;
+    private readonly IStoppingStrategy _stoppingStrategy;
+    private readonly IBusConfiguration _configuration;
+
+    private Task? _processPendingUnsubscriptionsTask;
+
+    private int _subscriptionsVersion;
+    private int _status;
+
+    public Bus(ITransport transport,
+               IPeerDirectory directory,
+               IMessageSerializer serializer,
+               IMessageDispatcher messageDispatcher,
+               IMessageSendingStrategy messageSendingStrategy,
+               IStoppingStrategy stoppingStrategy,
+               IBusConfiguration configuration)
     {
     {
-        private static readonly BusMessageLogger _messageLogger = new(typeof(Bus));
-        private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(Bus));
-
-        private readonly ConcurrentDictionary<MessageId, TaskCompletionSource<CommandResult>> _messageIdToTaskCompletionSources = new();
-        private readonly UniqueTimestampProvider _deserializationFailureTimestampProvider = new();
-        private readonly Dictionary<Subscription, SubscriptionStatus> _subscriptions = new();
-        private readonly List<IMessageHandlerInvoker> _startupInvokers = new();
-        private readonly HashSet<MessageTypeId> _pendingUnsubscriptions = new();
-        private readonly ITransport _transport;
-        private readonly IPeerDirectory _directory;
-        private readonly IMessageSerializer _serializer;
-        private readonly IMessageDispatcher _messageDispatcher;
-        private readonly IMessageSendingStrategy _messageSendingStrategy;
-        private readonly IStoppingStrategy _stoppingStrategy;
-        private readonly IBusConfiguration _configuration;
+        _transport = transport;
+        _transport.MessageReceived += OnTransportMessageReceived;
+        _directory = directory;
+        _directory.PeerUpdated += OnPeerUpdated;
+        _directory.PeerSubscriptionsUpdated += DispatchSubscriptionsUpdatedMessages;
+        _serializer = serializer;
+        _messageDispatcher = messageDispatcher;
+        _messageDispatcher.MessageHandlerInvokersUpdated += MessageDispatcherOnMessageHandlerInvokersUpdated;
+        _messageSendingStrategy = messageSendingStrategy;
+        _stoppingStrategy = stoppingStrategy;
+        _configuration = configuration;
+    }
 
 
-        private Task? _processPendingUnsubscriptionsTask;
+    public event Action? Starting;
+    public event Action? Started;
+    public event Action? Stopping;
+    public event Action? Stopped;
 
 
-        private int _subscriptionsVersion;
-        private int _status;
+    public PeerId PeerId { get; private set; }
+    public string Environment { get; private set; } = string.Empty;
+    public bool IsRunning => Status is BusStatus.Started or BusStatus.Stopping;
+    public string EndPoint => _transport.InboundEndPoint;
+    public string? DeserializationFailureDumpDirectoryPath { get; set; }
 
 
-        public Bus(ITransport transport,
-                   IPeerDirectory directory,
-                   IMessageSerializer serializer,
-                   IMessageDispatcher messageDispatcher,
-                   IMessageSendingStrategy messageSendingStrategy,
-                   IStoppingStrategy stoppingStrategy,
-                   IBusConfiguration configuration)
-        {
-            _transport = transport;
-            _transport.MessageReceived += OnTransportMessageReceived;
-            _directory = directory;
-            _directory.PeerUpdated += OnPeerUpdated;
-            _directory.PeerSubscriptionsUpdated += DispatchSubscriptionsUpdatedMessages;
-            _serializer = serializer;
-            _messageDispatcher = messageDispatcher;
-            _messageDispatcher.MessageHandlerInvokersUpdated += MessageDispatcherOnMessageHandlerInvokersUpdated;
-            _messageSendingStrategy = messageSendingStrategy;
-            _stoppingStrategy = stoppingStrategy;
-            _configuration = configuration;
-        }
+    private BusStatus Status
+    {
+        get => (BusStatus)_status;
+        set => _status = (int)value;
+    }
 
 
-        public event Action? Starting;
-        public event Action? Started;
-        public event Action? Stopping;
-        public event Action? Stopped;
+    public void Configure(PeerId peerId, string environment)
+    {
+        PeerId = peerId;
+        Environment = environment;
+        DeserializationFailureDumpDirectoryPath = Path.Combine(Path.GetTempPath(), $"zebus_deserialization_dumps_{peerId}");
+        _transport.Configure(peerId, environment);
+    }
 
 
-        public PeerId PeerId { get; private set; }
-        public string Environment { get; private set; } = string.Empty;
-        public bool IsRunning => Status == BusStatus.Started || Status == BusStatus.Stopping;
-        public string EndPoint => _transport.InboundEndPoint;
-        public string? DeserializationFailureDumpDirectoryPath { get; set; }
+    public virtual void Start()
+    {
+        if (Interlocked.CompareExchange(ref _status, (int)BusStatus.Starting, (int)BusStatus.Stopped) != (int)BusStatus.Stopped)
+            throw new InvalidOperationException("Unable to start, the bus is already running");
 
 
-        private BusStatus Status
+        try
         {
         {
-            get => (BusStatus)_status;
-            set => _status = (int)value;
+            Starting?.Invoke();
         }
         }
-
-        public void Configure(PeerId peerId, string environment)
+        catch
         {
         {
-            PeerId = peerId;
-            Environment = environment;
-            DeserializationFailureDumpDirectoryPath = Path.Combine(Path.GetTempPath(), $"zebus_deserialization_dumps_{peerId}");
-            _transport.Configure(peerId, environment);
+            Status = BusStatus.Stopped;
+            throw;
         }
         }
 
 
-        public virtual void Start()
+        var registered = false;
+        try
         {
         {
-            if (Interlocked.CompareExchange(ref _status, (int)BusStatus.Starting, (int)BusStatus.Stopped) != (int)BusStatus.Stopped)
-                throw new InvalidOperationException("Unable to start, the bus is already running");
+            _logger.LogDebug("Loading invokers...");
+            _messageDispatcher.LoadMessageHandlerInvokers();
 
 
-            try
-            {
-                Starting?.Invoke();
-            }
-            catch
-            {
-                Status = BusStatus.Stopped;
-                throw;
-            }
+            _logger.LogDebug("Performing startup subscribe...");
+            PerformStartupSubscribe();
 
 
-            var registered = false;
-            try
-            {
-                _logger.LogDebug("Loading invokers...");
-                _messageDispatcher.LoadMessageHandlerInvokers();
+            _logger.LogDebug("Starting transport...");
+            _transport.Start();
 
 
-                _logger.LogDebug("Performing startup subscribe...");
-                PerformStartupSubscribe();
+            Status = BusStatus.Started;
 
 
-                _logger.LogDebug("Starting transport...");
-                _transport.Start();
+            _logger.LogDebug("Registering on directory...");
+            var self = new Peer(PeerId, EndPoint);
+            _directory.RegisterAsync(this, self, GetSubscriptions()).Wait();
+            registered = true;
 
 
-                Status = BusStatus.Started;
+            _transport.OnRegistered();
 
 
-                _logger.LogDebug("Registering on directory...");
-                var self = new Peer(PeerId, EndPoint);
-                _directory.RegisterAsync(this, self, GetSubscriptions()).Wait();
-                registered = true;
+            _logger.LogDebug("Starting message dispatcher...");
+            _messageDispatcher.Start();
+        }
+        catch
+        {
+            InternalStop(registered);
+            Status = BusStatus.Stopped;
+            throw;
+        }
 
 
-                _transport.OnRegistered();
+        Started?.Invoke();
+    }
+
+    private void PerformStartupSubscribe()
+    {
+        var startupSubscriptions = _messageDispatcher.GetMessageHandlerInvokers()
+                                                     .SelectMany(x => x.GetStartupSubscriptions())
+                                                     .ToList();
 
 
-                _logger.LogDebug("Starting message dispatcher...");
-                _messageDispatcher.Start();
+        lock (_subscriptions)
+        {
+            foreach (var subscription in startupSubscriptions)
+            {
+                var status = GetOrAddSubscriptionStatus(subscription);
+                status.CurrentSubscriptionCount++;
             }
             }
-            catch
+
+            foreach (var status in _subscriptions.Values)
             {
             {
-                InternalStop(registered);
-                Status = BusStatus.Stopped;
-                throw;
+                status.CurrentSubscriptionCount += status.StartupSubscriptionCount;
             }
             }
 
 
-            Started?.Invoke();
+            foreach (var invoker in _startupInvokers)
+            {
+                _messageDispatcher.AddInvoker(invoker);
+            }
         }
         }
+    }
 
 
-        private void PerformStartupSubscribe()
+    protected virtual IEnumerable<Subscription> GetSubscriptions()
+    {
+        lock (_subscriptions)
         {
         {
-            var startupSubscriptions = _messageDispatcher.GetMessageHandlerInvokers()
-                                                         .SelectMany(x => x.GetStartupSubscriptions())
-                                                         .ToList();
+            return _subscriptions.Where(i => i.Value.CurrentSubscriptionCount > 0)
+                                 .Select(i => i.Key)
+                                 .ToList();
+        }
+    }
 
 
-            lock (_subscriptions)
-            {
-                foreach (var subscription in startupSubscriptions)
-                {
-                    var status = GetOrAddSubscriptionStatus(subscription);
-                    status.CurrentSubscriptionCount++;
-                }
+    private SubscriptionStatus GetOrAddSubscriptionStatus(Subscription subscription)
+    {
+        if (!_subscriptions.TryGetValue(subscription, out var status))
+        {
+            status = new SubscriptionStatus();
+            _subscriptions.Add(subscription, status);
+        }
 
 
-                foreach (var status in _subscriptions.Values)
-                {
-                    status.CurrentSubscriptionCount += status.StartupSubscriptionCount;
-                }
+        return status;
+    }
 
 
-                foreach (var invoker in _startupInvokers)
-                {
-                    _messageDispatcher.AddInvoker(invoker);
-                }
-            }
-        }
+    public virtual void Stop()
+    {
+        if (Interlocked.CompareExchange(ref _status, (int)BusStatus.Stopping, (int)BusStatus.Started) != (int)BusStatus.Started)
+            throw new InvalidOperationException("Unable to stop, the bus is not running");
 
 
-        protected virtual IEnumerable<Subscription> GetSubscriptions()
+        try
         {
         {
-            lock (_subscriptions)
-            {
-                return _subscriptions.Where(i => i.Value.CurrentSubscriptionCount > 0)
-                                     .Select(i => i.Key)
-                                     .ToList();
-            }
+            Stopping?.Invoke();
         }
         }
-
-        private SubscriptionStatus GetOrAddSubscriptionStatus(Subscription subscription)
+        catch
         {
         {
-            if (!_subscriptions.TryGetValue(subscription, out var status))
-            {
-                status = new SubscriptionStatus();
-                _subscriptions.Add(subscription, status);
-            }
-
-            return status;
+            Status = BusStatus.Started;
+            throw;
         }
         }
 
 
-        public virtual void Stop()
-        {
-            if (Interlocked.CompareExchange(ref _status, (int)BusStatus.Stopping, (int)BusStatus.Started) != (int)BusStatus.Started)
-                throw new InvalidOperationException("Unable to stop, the bus is not running");
+        InternalStop(true);
+
+        Stopped?.Invoke();
+    }
+
+    private void InternalStop(bool unregister)
+    {
+        Status = BusStatus.Stopping;
 
 
+        if (unregister)
+        {
             try
             try
             {
             {
-                Stopping?.Invoke();
+                _directory.UnregisterAsync(this).Wait();
             }
             }
-            catch
+            catch (Exception ex)
             {
             {
-                Status = BusStatus.Started;
-                throw;
+                _logger.LogError(ex, "Error during stop");
             }
             }
-
-            InternalStop(true);
-
-            Stopped?.Invoke();
         }
         }
 
 
-        private void InternalStop(bool unregister)
+        lock (_subscriptions)
         {
         {
-            Status = BusStatus.Stopping;
-
-            if (unregister)
+            foreach (var (subscription, status) in _subscriptions.ToList())
             {
             {
-                try
-                {
-                    _directory.UnregisterAsync(this).Wait();
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error during stop");
-                }
-            }
+                status.CurrentSubscriptionCount = 0;
 
 
-            lock (_subscriptions)
-            {
-                foreach (var (subscription, status) in _subscriptions.ToList())
-                {
-                    status.CurrentSubscriptionCount = 0;
-
-                    if (status.IsEmpty)
-                        _subscriptions.Remove(subscription);
-                }
-
-                _pendingUnsubscriptions.Clear();
-                _processPendingUnsubscriptionsTask = null;
-
-                unchecked
-                {
-                    ++_subscriptionsVersion;
-                }
+                if (status.IsEmpty)
+                    _subscriptions.Remove(subscription);
             }
             }
 
 
-            try
-            {
-                _stoppingStrategy.Stop(_transport, _messageDispatcher);
-            }
-            finally
+            _pendingUnsubscriptions.Clear();
+            _processPendingUnsubscriptionsTask = null;
+
+            unchecked
             {
             {
-                Status = BusStatus.Stopped;
+                ++_subscriptionsVersion;
             }
             }
-
-            _messageIdToTaskCompletionSources.Clear();
         }
         }
 
 
-        public void Publish(IEvent message)
+        try
         {
         {
-            if (!IsRunning)
-                throw new InvalidOperationException("Unable to publish message, the bus is not running");
+            _stoppingStrategy.Stop(_transport, _messageDispatcher);
+        }
+        finally
+        {
+            Status = BusStatus.Stopped;
+        }
 
 
-            var peersHandlingMessage = _directory.GetPeersHandlingMessage(message);
+        _messageIdToTaskCompletionSources.Clear();
+    }
 
 
-            PublishImpl(message, peersHandlingMessage);
-        }
+    public void Publish(IEvent message)
+    {
+        if (!IsRunning)
+            throw new InvalidOperationException("Unable to publish message, the bus is not running");
 
 
-        public void Publish(IEvent message, PeerId targetPeerId)
-        {
-            if (!IsRunning)
-                throw new InvalidOperationException("Unable to publish message, the bus is not running");
+        var peersHandlingMessage = _directory.GetPeersHandlingMessage(message);
 
 
-            var peer = _directory.GetPeer(targetPeerId);
-            if (peer != null)
-                PublishImpl(message, new List<Peer> { peer });
-        }
+        PublishImpl(message, peersHandlingMessage);
+    }
 
 
-        private void PublishImpl(IEvent message, IList<Peer> peers)
-        {
-            var localDispatchEnabled = LocalDispatch.Enabled;
-            var shouldBeHandledLocally = localDispatchEnabled && peers.Any(x => x.Id == PeerId);
-            if (shouldBeHandledLocally)
-                HandleLocalMessage(message, null);
+    public void Publish(IEvent message, PeerId targetPeerId)
+    {
+        if (!IsRunning)
+            throw new InvalidOperationException("Unable to publish message, the bus is not running");
 
 
-            var targetPeers = shouldBeHandledLocally ? peers.Where(x => x.Id != PeerId).ToList() : peers;
-            LogAndSendMessage(message, targetPeers, true, shouldBeHandledLocally);
-        }
+        var peer = _directory.GetPeer(targetPeerId);
+        if (peer != null)
+            PublishImpl(message, new List<Peer> { peer });
+    }
 
 
-        public Task<CommandResult> Send(ICommand message)
-        {
-            if (!IsRunning)
-                throw new InvalidOperationException("Unable to send message, the bus is not running");
+    private void PublishImpl(IEvent message, IList<Peer> peers)
+    {
+        var localDispatchEnabled = LocalDispatch.Enabled;
+        var shouldBeHandledLocally = localDispatchEnabled && peers.Any(x => x.Id == PeerId);
+        if (shouldBeHandledLocally)
+            HandleLocalMessage(message, null);
+
+        var targetPeers = shouldBeHandledLocally ? peers.Where(x => x.Id != PeerId).ToList() : peers;
+        LogAndSendMessage(message, targetPeers, true, shouldBeHandledLocally);
+    }
 
 
-            var peers = _directory.GetPeersHandlingMessage(message);
-            if (peers.Count == 0)
-                throw new InvalidOperationException("Unable to find peer for specified command, " + BusMessageLogger.ToString(message) + ". Did you change the namespace?");
+    public Task<CommandResult> Send(ICommand message)
+    {
+        if (!IsRunning)
+            throw new InvalidOperationException("Unable to send message, the bus is not running");
 
 
-            var self = peers.FirstOrDefault(x => x.Id == PeerId);
+        var peers = _directory.GetPeersHandlingMessage(message);
+        if (peers.Count == 0)
+            throw new InvalidOperationException("Unable to find peer for specified command, " + BusMessageLogger.ToString(message) + ". Did you change the namespace?");
 
 
-            if (self != null)
-                return SendImpl(message, self);
+        var self = peers.FirstOrDefault(x => x.Id == PeerId);
 
 
-            if (peers.Count > 1)
-            {
-                var exceptionMessage = $"{peers.Count} peers are handling {BusMessageLogger.ToString(message)}. Peers: {string.Join(", ", peers.Select(p => p.ToString()))}.";
-                throw new InvalidOperationException(exceptionMessage);
-            }
+        if (self != null)
+            return SendImpl(message, self);
 
 
-            return SendImpl(message, peers[0]);
+        if (peers.Count > 1)
+        {
+            var exceptionMessage = $"{peers.Count} peers are handling {BusMessageLogger.ToString(message)}. Peers: {string.Join(", ", peers.Select(p => p.ToString()))}.";
+            throw new InvalidOperationException(exceptionMessage);
         }
         }
 
 
-        public Task<CommandResult> Send(ICommand message, Peer peer)
-        {
-            if (peer == null)
-                throw new ArgumentNullException(nameof(peer));
+        return SendImpl(message, peers[0]);
+    }
 
 
-            if (!IsRunning)
-                throw new InvalidOperationException("Unable to send message, the bus is not running");
+    public Task<CommandResult> Send(ICommand message, Peer peer)
+    {
+        if (peer == null)
+            throw new ArgumentNullException(nameof(peer));
 
 
-            return SendImpl(message, peer);
-        }
+        if (!IsRunning)
+            throw new InvalidOperationException("Unable to send message, the bus is not running");
 
 
-        private Task<CommandResult> SendImpl(ICommand message, Peer peer)
-        {
-            var taskCompletionSource = new TaskCompletionSource<CommandResult>(TaskCreationOptions.RunContinuationsAsynchronously);
+        return SendImpl(message, peer);
+    }
 
 
-            if (LocalDispatch.Enabled && peer.Id == PeerId)
-            {
-                HandleLocalMessage(message, taskCompletionSource);
-            }
-            else
-            {
-                var transportMessage = ToTransportMessage(message);
+    private Task<CommandResult> SendImpl(ICommand message, Peer peer)
+    {
+        var taskCompletionSource = new TaskCompletionSource<CommandResult>(TaskCreationOptions.RunContinuationsAsynchronously);
 
 
-                if (!peer.IsResponding && !_messageSendingStrategy.IsMessagePersistent(transportMessage) && !message.TypeId().IsInfrastructure())
-                    throw new InvalidOperationException($"Unable to send this transient message {BusMessageLogger.ToString(message)} while peer {peer.Id} is not responding.");
+        if (LocalDispatch.Enabled && peer.Id == PeerId)
+        {
+            HandleLocalMessage(message, taskCompletionSource);
+        }
+        else
+        {
+            var transportMessage = ToTransportMessage(message);
 
 
-                _messageIdToTaskCompletionSources.TryAdd(transportMessage.Id, taskCompletionSource);
+            if (!peer.IsResponding && !_messageSendingStrategy.IsMessagePersistent(transportMessage) && !message.TypeId().IsInfrastructure())
+                throw new InvalidOperationException($"Unable to send this transient message {BusMessageLogger.ToString(message)} while peer {peer.Id} is not responding.");
 
 
-                var peers = new[] { peer };
-                _messageLogger.LogSendMessage(message, peers, transportMessage);
-                SendTransportMessage(transportMessage, peers);
-            }
+            _messageIdToTaskCompletionSources.TryAdd(transportMessage.Id, taskCompletionSource);
 
 
-            return taskCompletionSource.Task;
+            var peers = new[] { peer };
+            _messageLogger.LogSendMessage(message, peers, transportMessage);
+            SendTransportMessage(transportMessage, peers);
         }
         }
 
 
-        public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
-        {
-            if (request == null)
-                throw new ArgumentNullException(nameof(request));
+        return taskCompletionSource.Task;
+    }
 
 
-            if (IsRunning && !request.ThereIsNoHandlerButIKnowWhatIAmDoing)
-                EnsureMessageHandlerInvokerExists(request.Subscriptions);
+    public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
+    {
+        if (request == null)
+            throw new ArgumentNullException(nameof(request));
 
 
-            request.MarkAsSubmitted(_subscriptionsVersion, IsRunning);
+        if (IsRunning && !request.ThereIsNoHandlerButIKnowWhatIAmDoing)
+            EnsureMessageHandlerInvokerExists(request.Subscriptions);
 
 
-            if (!request.IsStartupRequest && request.Batch != null)
-                await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
+        request.MarkAsSubmitted(_subscriptionsVersion, IsRunning);
 
 
-            await AddSubscriptionsAsync(request).ConfigureAwait(false);
+        if (!request.IsStartupRequest && request.Batch != null)
+            await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
 
 
-            return new DisposableAction(() => RemoveSubscriptions(request));
-        }
+        await AddSubscriptionsAsync(request).ConfigureAwait(false);
 
 
-        public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
-        {
-            if (request == null)
-                throw new ArgumentNullException(nameof(request));
+        return new DisposableAction(() => RemoveSubscriptions(request));
+    }
 
 
-            if (handler == null)
-                throw new ArgumentNullException(nameof(handler));
+    public async Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
+    {
+        if (request == null)
+            throw new ArgumentNullException(nameof(request));
 
 
-            request.MarkAsSubmitted(_subscriptionsVersion, IsRunning);
+        if (handler == null)
+            throw new ArgumentNullException(nameof(handler));
 
 
-            var eventHandlerInvokers = request.Subscriptions
-                                              .GroupBy(x => x.MessageTypeId)
-                                              .Select(x => new DynamicMessageHandlerInvoker(
-                                                          handler,
-                                                          x.Key.GetMessageType() ?? throw new InvalidOperationException($"Could not resolve type {x.Key.FullName}"),
-                                                          x.Select(s => s.BindingKey).ToList()
-                                                      ))
-                                              .ToList();
+        request.MarkAsSubmitted(_subscriptionsVersion, IsRunning);
 
 
-            if (request.IsStartupRequest)
+        var eventHandlerInvokers = request.Subscriptions
+                                          .GroupBy(x => x.MessageTypeId)
+                                          .Select(x => new DynamicMessageHandlerInvoker(
+                                                      handler,
+                                                      x.Key.GetMessageType() ?? throw new InvalidOperationException($"Could not resolve type {x.Key.FullName}"),
+                                                      x.Select(s => s.BindingKey).ToList()
+                                                  ))
+                                          .ToList();
+
+        if (request.IsStartupRequest)
+        {
+            lock (_subscriptions)
             {
             {
-                lock (_subscriptions)
-                {
-                    _startupInvokers.AddRange(eventHandlerInvokers);
-                }
+                _startupInvokers.AddRange(eventHandlerInvokers);
             }
             }
-            else
-            {
-                if (request.Batch != null)
-                    await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
+        }
+        else
+        {
+            if (request.Batch != null)
+                await request.Batch.WhenSubmittedAsync().ConfigureAwait(false);
 
 
-                foreach (var eventHandlerInvoker in eventHandlerInvokers)
-                    _messageDispatcher.AddInvoker(eventHandlerInvoker);
-            }
+            foreach (var eventHandlerInvoker in eventHandlerInvokers)
+                _messageDispatcher.AddInvoker(eventHandlerInvoker);
+        }
 
 
-            await AddSubscriptionsAsync(request).ConfigureAwait(false);
+        await AddSubscriptionsAsync(request).ConfigureAwait(false);
 
 
-            return new DisposableAction(() =>
+        return new DisposableAction(() =>
+        {
+            if (request.IsStartupRequest)
             {
             {
-                if (request.IsStartupRequest)
+                lock (_subscriptions)
                 {
                 {
-                    lock (_subscriptions)
-                    {
-                        _startupInvokers.RemoveRange(eventHandlerInvokers);
-                    }
+                    _startupInvokers.RemoveRange(eventHandlerInvokers);
                 }
                 }
+            }
 
 
-                foreach (var eventHandlerInvoker in eventHandlerInvokers)
-                    _messageDispatcher.RemoveInvoker(eventHandlerInvoker);
+            foreach (var eventHandlerInvoker in eventHandlerInvokers)
+                _messageDispatcher.RemoveInvoker(eventHandlerInvoker);
 
 
-                RemoveSubscriptions(request);
-            });
-        }
+            RemoveSubscriptions(request);
+        });
+    }
 
 
-        private void EnsureMessageHandlerInvokerExists(IEnumerable<Subscription> subscriptions)
+    private void EnsureMessageHandlerInvokerExists(IEnumerable<Subscription> subscriptions)
+    {
+        foreach (var subscription in subscriptions)
         {
         {
-            foreach (var subscription in subscriptions)
-            {
-                if (_messageDispatcher.GetMessageHandlerInvokers().All(x => x.MessageTypeId != subscription.MessageTypeId))
-                    throw new ArgumentException($"No handler available for message type Id: {subscription.MessageTypeId}");
-            }
+            if (_messageDispatcher.GetMessageHandlerInvokers().All(x => x.MessageTypeId != subscription.MessageTypeId))
+                throw new ArgumentException($"No handler available for message type Id: {subscription.MessageTypeId}");
         }
         }
+    }
 
 
-        private async Task AddSubscriptionsAsync(SubscriptionRequest request)
+    private async Task AddSubscriptionsAsync(SubscriptionRequest request)
+    {
+        if (request.IsStartupRequest)
         {
         {
-            if (request.IsStartupRequest)
+            lock (_subscriptions)
             {
             {
-                lock (_subscriptions)
+                foreach (var subscription in request.Subscriptions)
                 {
                 {
-                    foreach (var subscription in request.Subscriptions)
-                    {
-                        var status = GetOrAddSubscriptionStatus(subscription);
-                        status.StartupSubscriptionCount++;
-                    }
-
-                    if (!IsRunning)
-                        return;
+                    var status = GetOrAddSubscriptionStatus(subscription);
+                    status.StartupSubscriptionCount++;
                 }
                 }
+
+                if (!IsRunning)
+                    return;
             }
             }
+        }
 
 
-            if (request.Batch != null)
+        if (request.Batch != null)
+        {
+            var batchSubscriptions = request.Batch.TryConsumeBatchSubscriptions();
+            if (batchSubscriptions != null)
             {
             {
-                var batchSubscriptions = request.Batch.TryConsumeBatchSubscriptions();
-                if (batchSubscriptions != null)
+                try
                 {
                 {
-                    try
-                    {
-                        await SendSubscriptionsAsync(batchSubscriptions).ConfigureAwait(false);
-                        request.Batch.NotifyRegistrationCompleted(null);
-                    }
-                    catch (Exception ex)
-                    {
-                        request.Batch.NotifyRegistrationCompleted(ex);
-                        throw;
-                    }
+                    await SendSubscriptionsAsync(batchSubscriptions).ConfigureAwait(false);
+                    request.Batch.NotifyRegistrationCompleted(null);
                 }
                 }
-                else
+                catch (Exception ex)
                 {
                 {
-                    await request.Batch.WhenRegistrationCompletedAsync().ConfigureAwait(false);
+                    request.Batch.NotifyRegistrationCompleted(ex);
+                    throw;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                await SendSubscriptionsAsync(request.Subscriptions).ConfigureAwait(false);
+                await request.Batch.WhenRegistrationCompletedAsync().ConfigureAwait(false);
             }
             }
+        }
+        else
+        {
+            await SendSubscriptionsAsync(request.Subscriptions).ConfigureAwait(false);
+        }
 
 
-            async Task SendSubscriptionsAsync(IEnumerable<Subscription> subscriptions)
-            {
-                if (request.SubmissionSubscriptionsVersion != _subscriptionsVersion)
-                    throw new InvalidOperationException("The bus has been stopped before the subscriptions have been sent");
+        async Task SendSubscriptionsAsync(IEnumerable<Subscription> subscriptions)
+        {
+            if (request.SubmissionSubscriptionsVersion != _subscriptionsVersion)
+                throw new InvalidOperationException("The bus has been stopped before the subscriptions have been sent");
 
 
-                var updatedTypes = new HashSet<MessageTypeId>();
+            var updatedTypes = new HashSet<MessageTypeId>();
 
 
-                lock (_subscriptions)
+            lock (_subscriptions)
+            {
+                foreach (var subscription in subscriptions)
                 {
                 {
-                    foreach (var subscription in subscriptions)
-                    {
-                        var status = GetOrAddSubscriptionStatus(subscription);
-                        status.CurrentSubscriptionCount++;
+                    var status = GetOrAddSubscriptionStatus(subscription);
+                    status.CurrentSubscriptionCount++;
 
 
-                        if (status.CurrentSubscriptionCount <= 1)
-                            updatedTypes.Add(subscription.MessageTypeId);
-                    }
+                    if (status.CurrentSubscriptionCount <= 1)
+                        updatedTypes.Add(subscription.MessageTypeId);
                 }
                 }
+            }
 
 
-                if (updatedTypes.Count != 0)
-                {
-                    // Wait until all unsubscriptions are completed to prevent race conditions
-                    await WhenUnsubscribeCompletedAsync().ConfigureAwait(false);
-                    await UpdateDirectorySubscriptionsAsync(updatedTypes).ConfigureAwait(false);
-                }
+            if (updatedTypes.Count != 0)
+            {
+                // Wait until all unsubscriptions are completed to prevent race conditions
+                await WhenUnsubscribeCompletedAsync().ConfigureAwait(false);
+                await UpdateDirectorySubscriptionsAsync(updatedTypes).ConfigureAwait(false);
             }
             }
         }
         }
+    }
 
 
-        internal async Task WhenUnsubscribeCompletedAsync()
-        {
-            Task? task;
+    internal async Task WhenUnsubscribeCompletedAsync()
+    {
+        Task? task;
 
 
-            lock (_subscriptions)
-            {
-                task = _processPendingUnsubscriptionsTask;
-            }
+        lock (_subscriptions)
+        {
+            task = _processPendingUnsubscriptionsTask;
+        }
 
 
-            if (task == null)
-                return;
+        if (task == null)
+            return;
 
 
-            try
-            {
-                await task.ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error waiting for pending unsubscription");
-            }
+        try
+        {
+            await task.ConfigureAwait(false);
         }
         }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error waiting for pending unsubscription");
+        }
+    }
 
 
-        private void RemoveSubscriptions(SubscriptionRequest request)
+    private void RemoveSubscriptions(SubscriptionRequest request)
+    {
+        lock (_subscriptions)
         {
         {
-            lock (_subscriptions)
+            if (request.IsStartupRequest)
             {
             {
-                if (request.IsStartupRequest)
-                {
-                    foreach (var subscription in request.Subscriptions)
-                    {
-                        if (!_subscriptions.TryGetValue(subscription, out var status))
-                            continue;
-
-                        status.StartupSubscriptionCount--;
-
-                        if (status.IsEmpty)
-                            _subscriptions.Remove(subscription);
-                    }
-
-                    if (!IsRunning)
-                        return;
-                }
-                else
-                {
-                    if (!IsRunning || request.SubmissionSubscriptionsVersion != _subscriptionsVersion)
-                        return;
-                }
-
                 foreach (var subscription in request.Subscriptions)
                 foreach (var subscription in request.Subscriptions)
                 {
                 {
                     if (!_subscriptions.TryGetValue(subscription, out var status))
                     if (!_subscriptions.TryGetValue(subscription, out var status))
                         continue;
                         continue;
 
 
-                    status.CurrentSubscriptionCount--;
+                    status.StartupSubscriptionCount--;
 
 
-                    if (status.CurrentSubscriptionCount <= 0)
-                    {
-                        if (status.IsEmpty)
-                            _subscriptions.Remove(subscription);
-
-                        _pendingUnsubscriptions.Add(subscription.MessageTypeId);
-                    }
+                    if (status.IsEmpty)
+                        _subscriptions.Remove(subscription);
                 }
                 }
 
 
-                if (_pendingUnsubscriptions.Count != 0 && _processPendingUnsubscriptionsTask?.IsCompleted != false)
+                if (!IsRunning)
+                    return;
+            }
+            else
+            {
+                if (!IsRunning || request.SubmissionSubscriptionsVersion != _subscriptionsVersion)
+                    return;
+            }
+
+            foreach (var subscription in request.Subscriptions)
+            {
+                if (!_subscriptions.TryGetValue(subscription, out var status))
+                    continue;
+
+                status.CurrentSubscriptionCount--;
+
+                if (status.CurrentSubscriptionCount <= 0)
                 {
                 {
-                    var subscriptionsVersion = _subscriptionsVersion;
-                    _processPendingUnsubscriptionsTask = Task.Run(() => ProcessPendingUnsubscriptions(subscriptionsVersion));
+                    if (status.IsEmpty)
+                        _subscriptions.Remove(subscription);
+
+                    _pendingUnsubscriptions.Add(subscription.MessageTypeId);
                 }
                 }
             }
             }
+
+            if (_pendingUnsubscriptions.Count != 0 && _processPendingUnsubscriptionsTask?.IsCompleted != false)
+            {
+                var subscriptionsVersion = _subscriptionsVersion;
+                _processPendingUnsubscriptionsTask = Task.Run(() => ProcessPendingUnsubscriptions(subscriptionsVersion));
+            }
         }
         }
+    }
 
 
-        private async Task ProcessPendingUnsubscriptions(int subscriptionsVersion)
+    private async Task ProcessPendingUnsubscriptions(int subscriptionsVersion)
+    {
+        try
         {
         {
-            try
+            var updatedTypes = new HashSet<MessageTypeId>();
+
+            while (true)
             {
             {
-                var updatedTypes = new HashSet<MessageTypeId>();
+                updatedTypes.Clear();
 
 
-                while (true)
+                lock (_subscriptions)
                 {
                 {
-                    updatedTypes.Clear();
+                    updatedTypes.UnionWith(_pendingUnsubscriptions);
+                    _pendingUnsubscriptions.Clear();
 
 
-                    lock (_subscriptions)
+                    if (updatedTypes.Count == 0 || !IsRunning || Status == BusStatus.Stopping || subscriptionsVersion != _subscriptionsVersion)
                     {
                     {
-                        updatedTypes.UnionWith(_pendingUnsubscriptions);
-                        _pendingUnsubscriptions.Clear();
-
-                        if (updatedTypes.Count == 0 || !IsRunning || Status == BusStatus.Stopping || subscriptionsVersion != _subscriptionsVersion)
-                        {
-                            _processPendingUnsubscriptionsTask = null;
-                            return;
-                        }
+                        _processPendingUnsubscriptionsTask = null;
+                        return;
                     }
                     }
-
-                    await UpdateDirectorySubscriptionsAsync(updatedTypes).ConfigureAwait(false);
                 }
                 }
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error processing pending unsubscription");
 
 
-                lock (_subscriptions)
-                {
-                    _processPendingUnsubscriptionsTask = null;
-                }
+                await UpdateDirectorySubscriptionsAsync(updatedTypes).ConfigureAwait(false);
             }
             }
         }
         }
-
-        private async Task UpdateDirectorySubscriptionsAsync(HashSet<MessageTypeId> updatedTypes)
+        catch (Exception ex)
         {
         {
-            var subscriptions = GetSubscriptions().Where(sub => updatedTypes.Contains(sub.MessageTypeId));
-            var subscriptionsByTypes = SubscriptionsForType.CreateDictionary(subscriptions);
+            _logger.LogError(ex, "Error processing pending unsubscription");
 
 
-            var subscriptionUpdates = new List<SubscriptionsForType>(updatedTypes.Count);
-            foreach (var updatedMessageId in updatedTypes)
-                subscriptionUpdates.Add(subscriptionsByTypes.GetValueOrDefault(updatedMessageId, new SubscriptionsForType(updatedMessageId)));
-
-            await _directory.UpdateSubscriptionsAsync(this, subscriptionUpdates).ConfigureAwait(false);
+            lock (_subscriptions)
+            {
+                _processPendingUnsubscriptionsTask = null;
+            }
         }
         }
+    }
 
 
-        public void Reply(int errorCode)
-            => Reply(errorCode, null);
+    private async Task UpdateDirectorySubscriptionsAsync(HashSet<MessageTypeId> updatedTypes)
+    {
+        var subscriptions = GetSubscriptions().Where(sub => updatedTypes.Contains(sub.MessageTypeId));
+        var subscriptionsByTypes = SubscriptionsForType.CreateDictionary(subscriptions);
 
 
-        public void Reply(int errorCode, string? message)
-        {
-            var messageContext = MessageContext.Current;
-            if (messageContext == null)
-                throw new InvalidOperationException("Reply called without message context");
+        var subscriptionUpdates = new List<SubscriptionsForType>(updatedTypes.Count);
+        foreach (var updatedMessageId in updatedTypes)
+            subscriptionUpdates.Add(subscriptionsByTypes.GetValueOrDefault(updatedMessageId, new SubscriptionsForType(updatedMessageId)));
 
 
-            messageContext.ReplyCode = errorCode;
-            messageContext.ReplyMessage = message;
-        }
+        await _directory.UpdateSubscriptionsAsync(this, subscriptionUpdates).ConfigureAwait(false);
+    }
 
 
-        public void Reply(IMessage? response)
-        {
-            var messageContext = MessageContext.Current;
-            if (messageContext == null)
-                throw new InvalidOperationException("Reply called without message context");
+    public void Reply(int errorCode)
+        => Reply(errorCode, null);
 
 
-            messageContext.ReplyResponse = response;
-        }
+    public void Reply(int errorCode, string? message)
+    {
+        var messageContext = MessageContext.Current;
+        if (messageContext == null)
+            throw new InvalidOperationException("Reply called without message context");
 
 
-        private void OnPeerUpdated(PeerId peerId, PeerUpdateAction peerUpdateAction)
-            => _transport.OnPeerUpdated(peerId, peerUpdateAction);
+        messageContext.ReplyCode = errorCode;
+        messageContext.ReplyMessage = message;
+    }
 
 
-        private void OnTransportMessageReceived(TransportMessage transportMessage)
-        {
-            if (transportMessage.MessageTypeId == MessageExecutionCompleted.TypeId)
-            {
-                HandleMessageExecutionCompleted(transportMessage);
-            }
-            else
-            {
-                var executeSynchronously = transportMessage.MessageTypeId.IsInfrastructure();
-                HandleRemoteMessage(transportMessage, executeSynchronously);
-            }
-        }
+    public void Reply(IMessage? response)
+    {
+        var messageContext = MessageContext.Current;
+        if (messageContext == null)
+            throw new InvalidOperationException("Reply called without message context");
 
 
-        public MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage)
-            => CreateMessageDispatch(transportMessage, synchronousDispatch: false, sendAcknowledgment: false);
+        messageContext.ReplyResponse = response;
+    }
 
 
-        private MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage, bool synchronousDispatch, bool sendAcknowledgment = true)
-        {
-            var message = ToMessage(transportMessage);
-            if (message == null)
-                return null;
+    private void OnPeerUpdated(PeerId peerId, PeerUpdateAction peerUpdateAction)
+        => _transport.OnPeerUpdated(peerId, peerUpdateAction);
 
 
-            var context = MessageContext.CreateNew(transportMessage);
-            var continuation = GetOnRemoteMessageDispatchedContinuation(transportMessage, sendAcknowledgment);
-            return new MessageDispatch(context, message, _serializer, continuation, synchronousDispatch);
+    private void OnTransportMessageReceived(TransportMessage transportMessage)
+    {
+        if (transportMessage.MessageTypeId == MessageExecutionCompleted.TypeId)
+        {
+            HandleMessageExecutionCompleted(transportMessage);
         }
         }
-
-        protected virtual void HandleRemoteMessage(TransportMessage transportMessage, bool synchronous = false)
+        else
         {
         {
-            var dispatch = CreateMessageDispatch(transportMessage, synchronous);
-            if (dispatch == null)
-            {
-                _logger.LogWarning($"Received a remote message that could not be deserialized: {transportMessage.MessageTypeId.FullName} from {transportMessage.Originator.SenderId}");
-                _transport.AckMessage(transportMessage);
-                return;
-            }
-
-            _messageLogger.LogReceiveMessageRemote(dispatch.Message, transportMessage);
-            _messageDispatcher.Dispatch(dispatch);
+            var executeSynchronously = transportMessage.MessageTypeId.IsInfrastructure();
+            HandleRemoteMessage(transportMessage, executeSynchronously);
         }
         }
+    }
 
 
-        private Action<MessageDispatch, DispatchResult> GetOnRemoteMessageDispatchedContinuation(TransportMessage transportMessage, bool sendAcknowledgment)
-        {
-            return (dispatch, dispatchResult) =>
-            {
-                HandleDispatchErrors(dispatch, dispatchResult, transportMessage);
+    public MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage)
+        => CreateMessageDispatch(transportMessage, synchronousDispatch: false, sendAcknowledgment: false);
 
 
-                if (!sendAcknowledgment)
-                    return;
+    private MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage, bool synchronousDispatch, bool sendAcknowledgment = true)
+    {
+        var message = ToMessage(transportMessage);
+        if (message == null)
+            return null;
 
 
-                if (dispatch.Message is ICommand && !(dispatch.Message is PersistMessageCommand))
-                {
-                    var messageExecutionCompleted = MessageExecutionCompleted.Create(dispatch.Context, dispatchResult, _serializer);
-                    var shouldLogMessageExecutionCompleted = _messageLogger.IsInfoEnabled(dispatch.Message);
-                    LogAndSendMessage(messageExecutionCompleted, new[] { dispatch.Context.GetSender() }, shouldLogMessageExecutionCompleted);
-                }
+        var context = MessageContext.CreateNew(transportMessage);
+        var continuation = GetOnRemoteMessageDispatchedContinuation(transportMessage, sendAcknowledgment);
+        return new MessageDispatch(context, message, _serializer, continuation, synchronousDispatch);
+    }
 
 
-                AckTransportMessage(transportMessage);
-            };
+    protected virtual void HandleRemoteMessage(TransportMessage transportMessage, bool synchronous = false)
+    {
+        var dispatch = CreateMessageDispatch(transportMessage, synchronous);
+        if (dispatch == null)
+        {
+            _logger.LogWarning($"Received a remote message that could not be deserialized: {transportMessage.MessageTypeId.FullName} from {transportMessage.Originator.SenderId}");
+            _transport.AckMessage(transportMessage);
+            return;
         }
         }
 
 
-        private void HandleDispatchErrors(MessageDispatch dispatch, DispatchResult dispatchResult, TransportMessage? failingTransportMessage = null)
-        {
-            if (!_configuration.IsErrorPublicationEnabled || !IsRunning || dispatchResult.Errors.Count == 0 || dispatchResult.Errors.All(error => error is MessageProcessingException { ShouldPublishError: false }))
-                return;
+        _messageLogger.LogReceiveMessageRemote(dispatch.Message, transportMessage);
+        _messageDispatcher.Dispatch(dispatch);
+    }
 
 
-            var errorMessages = dispatchResult.Errors.Select(error => error.ToString());
-            var errorMessage = string.Join(System.Environment.NewLine + System.Environment.NewLine, errorMessages);
+    private Action<MessageDispatch, DispatchResult> GetOnRemoteMessageDispatchedContinuation(TransportMessage transportMessage, bool sendAcknowledgment)
+    {
+        return (dispatch, dispatchResult) =>
+        {
+            HandleDispatchErrors(dispatch, dispatchResult, transportMessage);
 
 
-            try
-            {
-                failingTransportMessage ??= ToTransportMessage(dispatch.Message);
-            }
-            catch (Exception ex)
-            {
-                HandleDispatchErrorsForUnserializableMessage(dispatch.Message, ex, errorMessage);
+            if (!sendAcknowledgment)
                 return;
                 return;
-            }
 
 
-            string jsonMessage;
-            try
-            {
-                jsonMessage = JsonConvert.SerializeObject(dispatch.Message);
-            }
-            catch (Exception ex)
+            if (dispatch.Message is ICommand && !(dispatch.Message is PersistMessageCommand))
             {
             {
-                jsonMessage = $"Unable to serialize message :{System.Environment.NewLine}{ex}";
+                var messageExecutionCompleted = MessageExecutionCompleted.Create(dispatch.Context, dispatchResult, _serializer);
+                var shouldLogMessageExecutionCompleted = _messageLogger.IsInfoEnabled(dispatch.Message);
+                LogAndSendMessage(messageExecutionCompleted, new[] { dispatch.Context.GetSender() }, shouldLogMessageExecutionCompleted);
             }
             }
 
 
-            var messageProcessingFailed = new MessageProcessingFailed(failingTransportMessage, jsonMessage, errorMessage, SystemDateTime.UtcNow, dispatchResult.ErrorHandlerTypes.Select(x => x.FullName!).ToArray());
-            Publish(messageProcessingFailed);
-        }
+            AckTransportMessage(transportMessage);
+        };
+    }
 
 
-        private void HandleDispatchErrorsForUnserializableMessage(IMessage message, Exception serializationException, string dispatchErrorMessage)
-        {
-            var messageTypeName = message.GetType().FullName;
-            _logger.LogError(serializationException, $"Unable to serialize message {messageTypeName}");
+    private void HandleDispatchErrors(MessageDispatch dispatch, DispatchResult dispatchResult, TransportMessage? failingTransportMessage = null)
+    {
+        if (!_configuration.IsErrorPublicationEnabled || !IsRunning || dispatchResult.Errors.Count == 0 || dispatchResult.Errors.All(error => error is MessageProcessingException { ShouldPublishError: false }))
+            return;
 
 
-            if (!_configuration.IsErrorPublicationEnabled || !IsRunning)
-                return;
+        var errorMessages = dispatchResult.Errors.Select(error => error.ToString());
+        var errorMessage = string.Join(System.Environment.NewLine + System.Environment.NewLine, errorMessages);
 
 
-            var errorMessage = $"Unable to handle local message\r\nMessage is not serializable\r\nMessageType: {messageTypeName}\r\nDispatch error: {dispatchErrorMessage}\r\nSerialization error: {serializationException}";
-            var processingFailed = new CustomProcessingFailed(GetType().FullName!, errorMessage, SystemDateTime.UtcNow);
-            Publish(processingFailed);
+        try
+        {
+            failingTransportMessage ??= ToTransportMessage(dispatch.Message);
         }
         }
-
-        private void HandleMessageExecutionCompleted(TransportMessage transportMessage)
+        catch (Exception ex)
         {
         {
-            var message = (MessageExecutionCompleted?)ToMessage(transportMessage);
-            if (message == null)
-                return;
-
-            HandleMessageExecutionCompleted(transportMessage, message);
+            HandleDispatchErrorsForUnserializableMessage(dispatch.Message, ex, errorMessage);
+            return;
         }
         }
 
 
-        protected virtual void HandleMessageExecutionCompleted(TransportMessage transportMessage, MessageExecutionCompleted message)
+        string jsonMessage;
+        try
+        {
+            jsonMessage = JsonConvert.SerializeObject(dispatch.Message);
+        }
+        catch (Exception ex)
         {
         {
-            _messageLogger.LogReceiveMessageAck(message);
+            jsonMessage = $"Unable to serialize message :{System.Environment.NewLine}{ex}";
+        }
 
 
-            if (!_messageIdToTaskCompletionSources.TryRemove(message.SourceCommandId, out var taskCompletionSource))
-                return;
+        var messageProcessingFailed = new MessageProcessingFailed(failingTransportMessage, jsonMessage, errorMessage, SystemDateTime.UtcNow, dispatchResult.ErrorHandlerTypes.Select(x => x.FullName!).ToArray());
+        Publish(messageProcessingFailed);
+    }
 
 
-            var response = message.PayloadTypeId != null ? ToMessage(message.PayloadTypeId.Value, message.Payload, transportMessage) : null;
-            var commandResult = new CommandResult(message.ErrorCode, message.ResponseMessage, response);
+    private void HandleDispatchErrorsForUnserializableMessage(IMessage message, Exception serializationException, string dispatchErrorMessage)
+    {
+        var messageTypeName = message.GetType().FullName;
+        _logger.LogError(serializationException, $"Unable to serialize message {messageTypeName}");
 
 
-            taskCompletionSource.SetResult(commandResult);
-        }
+        if (!_configuration.IsErrorPublicationEnabled || !IsRunning)
+            return;
 
 
-        protected virtual void HandleLocalMessage(IMessage message, TaskCompletionSource<CommandResult>? taskCompletionSource)
-        {
-            _messageLogger.LogReceiveMessageLocal(message);
+        var errorMessage = $"Unable to handle local message\r\nMessage is not serializable\r\nMessageType: {messageTypeName}\r\nDispatch error: {dispatchErrorMessage}\r\nSerialization error: {serializationException}";
+        var processingFailed = new CustomProcessingFailed(GetType().FullName!, errorMessage, SystemDateTime.UtcNow);
+        Publish(processingFailed);
+    }
 
 
-            var context = MessageContext.CreateOverride(PeerId, EndPoint);
-            var dispatch = new MessageDispatch(context, message, _serializer, GetOnLocalMessageDispatchedContinuation(taskCompletionSource))
-            {
-                IsLocal = true
-            };
+    private void HandleMessageExecutionCompleted(TransportMessage transportMessage)
+    {
+        var message = (MessageExecutionCompleted?)ToMessage(transportMessage);
+        if (message == null)
+            return;
 
 
-            _messageDispatcher.Dispatch(dispatch);
-        }
+        HandleMessageExecutionCompleted(transportMessage, message);
+    }
 
 
-        private Action<MessageDispatch, DispatchResult> GetOnLocalMessageDispatchedContinuation(TaskCompletionSource<CommandResult>? taskCompletionSource)
-        {
-            return (dispatch, dispatchResult) =>
-            {
-                HandleDispatchErrors(dispatch, dispatchResult);
+    protected virtual void HandleMessageExecutionCompleted(TransportMessage transportMessage, MessageExecutionCompleted message)
+    {
+        _messageLogger.LogReceiveMessageAck(message);
 
 
-                if (taskCompletionSource == null)
-                    return;
+        if (!_messageIdToTaskCompletionSources.TryRemove(message.SourceCommandId, out var taskCompletionSource))
+            return;
 
 
-                var errorStatus = dispatchResult.Errors.Any() ? CommandResult.GetErrorStatus(dispatchResult.Errors) : dispatch.Context.GetErrorStatus();
-                var commandResult = new CommandResult(errorStatus.Code, errorStatus.Message, dispatch.Context.ReplyResponse);
-                taskCompletionSource.SetResult(commandResult);
-            };
-        }
+        var response = message.PayloadTypeId != null ? ToMessage(message.PayloadTypeId.Value, message.Payload, transportMessage) : null;
+        var commandResult = new CommandResult(message.ErrorCode, message.ResponseMessage, response);
+
+        taskCompletionSource.SetResult(commandResult);
+    }
 
 
-        private void LogAndSendMessage(IMessage message, IList<Peer> peers, bool logEnabled, bool locallyHandled = false)
+    protected virtual void HandleLocalMessage(IMessage message, TaskCompletionSource<CommandResult>? taskCompletionSource)
+    {
+        _messageLogger.LogReceiveMessageLocal(message);
+
+        var context = MessageContext.CreateOverride(PeerId, EndPoint);
+        var dispatch = new MessageDispatch(context, message, _serializer, GetOnLocalMessageDispatchedContinuation(taskCompletionSource))
         {
         {
-            if (peers.Count == 0)
-            {
-                if (!locallyHandled && logEnabled)
-                    _messageLogger.LogSendMessage(message, peers);
+            IsLocal = true
+        };
 
 
+        _messageDispatcher.Dispatch(dispatch);
+    }
+
+    private Action<MessageDispatch, DispatchResult> GetOnLocalMessageDispatchedContinuation(TaskCompletionSource<CommandResult>? taskCompletionSource)
+    {
+        return (dispatch, dispatchResult) =>
+        {
+            HandleDispatchErrors(dispatch, dispatchResult);
+
+            if (taskCompletionSource == null)
                 return;
                 return;
-            }
 
 
-            var transportMessage = ToTransportMessage(message);
+            var errorStatus = dispatchResult.Errors.Any() ? CommandResult.GetErrorStatus(dispatchResult.Errors) : dispatch.Context.GetErrorStatus();
+            var commandResult = new CommandResult(errorStatus.Code, errorStatus.Message, dispatch.Context.ReplyResponse);
+            taskCompletionSource.SetResult(commandResult);
+        };
+    }
 
 
-            if (logEnabled)
-                _messageLogger.LogSendMessage(message, peers, transportMessage);
+    private void LogAndSendMessage(IMessage message, IList<Peer> peers, bool logEnabled, bool locallyHandled = false)
+    {
+        if (peers.Count == 0)
+        {
+            if (!locallyHandled && logEnabled)
+                _messageLogger.LogSendMessage(message, peers);
 
 
-            SendTransportMessage(transportMessage, peers);
+            return;
         }
         }
 
 
-        protected void SendTransportMessage(TransportMessage transportMessage, IList<Peer> peers)
-            => _transport.Send(transportMessage, peers, new SendContext());
+        var transportMessage = ToTransportMessage(message);
 
 
-        protected void AckTransportMessage(TransportMessage transportMessage)
-            => _transport.AckMessage(transportMessage);
+        if (logEnabled)
+            _messageLogger.LogSendMessage(message, peers, transportMessage);
 
 
-        protected TransportMessage ToTransportMessage(IMessage message)
-            => _serializer.ToTransportMessage(message, PeerId, EndPoint);
+        SendTransportMessage(transportMessage, peers);
+    }
 
 
-        private IMessage? ToMessage(TransportMessage transportMessage)
-            => ToMessage(transportMessage.MessageTypeId, transportMessage.Content, transportMessage);
+    protected void SendTransportMessage(TransportMessage transportMessage, IList<Peer> peers)
+        => _transport.Send(transportMessage, peers, new SendContext());
 
 
-        private IMessage? ToMessage(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent, TransportMessage transportMessage)
-        {
-            try
-            {
-                return _serializer.ToMessage(transportMessage, messageTypeId, messageContent);
-            }
-            catch (Exception exception)
-            {
-                HandleDeserializationError(messageTypeId, messageContent, transportMessage.Originator, exception, transportMessage);
-            }
+    protected void AckTransportMessage(TransportMessage transportMessage)
+        => _transport.AckMessage(transportMessage);
 
 
-            return null;
-        }
+    protected TransportMessage ToTransportMessage(IMessage message)
+        => _serializer.ToTransportMessage(message, PeerId, EndPoint);
+
+    private IMessage? ToMessage(TransportMessage transportMessage)
+        => ToMessage(transportMessage.MessageTypeId, transportMessage.Content, transportMessage);
 
 
-        private void HandleDeserializationError(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent, OriginatorInfo originator, Exception exception, TransportMessage transportMessage)
+    private IMessage? ToMessage(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent, TransportMessage transportMessage)
+    {
+        try
         {
         {
-            var dumpLocation = DumpMessageOnDisk(messageTypeId, messageContent);
-            var errorMessage = $"Unable to deserialize message {messageTypeId.FullName}. MessageId: {transportMessage.Id}. Originator: {originator.SenderId}. Message dumped at: {dumpLocation}\r\n{exception}";
-            _logger.LogError(errorMessage);
+            return _serializer.ToMessage(transportMessage, messageTypeId, messageContent);
+        }
+        catch (Exception exception)
+        {
+            HandleDeserializationError(messageTypeId, messageContent, transportMessage.Originator, exception, transportMessage);
+        }
 
 
-            if (!_configuration.IsErrorPublicationEnabled || !IsRunning)
-                return;
+        return null;
+    }
 
 
-            var processingFailed = new MessageProcessingFailed(transportMessage, string.Empty, errorMessage, SystemDateTime.UtcNow, null);
-            Publish(processingFailed);
-        }
+    private void HandleDeserializationError(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent, OriginatorInfo originator, Exception exception, TransportMessage transportMessage)
+    {
+        var dumpLocation = DumpMessageOnDisk(messageTypeId, messageContent);
+        var errorMessage = $"Unable to deserialize message {messageTypeId.FullName}. MessageId: {transportMessage.Id}. Originator: {originator.SenderId}. Message dumped at: {dumpLocation}\r\n{exception}";
+        _logger.LogError(errorMessage);
 
 
-        private void MessageDispatcherOnMessageHandlerInvokersUpdated()
-        {
-            var snapshotGeneratingMessageTypes = _messageDispatcher.GetMessageHandlerInvokers()
-                                                                   .Select(x => x.MessageHandlerType.GetBaseTypes().SingleOrDefault(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(SubscriptionHandler<>))?.GenericTypeArguments[0])
-                                                                   .Where(x => x != null);
+        if (!_configuration.IsErrorPublicationEnabled || !IsRunning)
+            return;
 
 
-            _directory.EnableSubscriptionsUpdatedFor(snapshotGeneratingMessageTypes!);
-        }
+        var processingFailed = new MessageProcessingFailed(transportMessage, string.Empty, errorMessage, SystemDateTime.UtcNow, null);
+        Publish(processingFailed);
+    }
 
 
-        private void DispatchSubscriptionsUpdatedMessages(PeerId peerId, IReadOnlyList<Subscription> subscriptions)
-        {
-            if (peerId == PeerId)
-                return;
+    private void MessageDispatcherOnMessageHandlerInvokersUpdated()
+    {
+        var snapshotGeneratingMessageTypes = _messageDispatcher.GetMessageHandlerInvokers()
+                                                               .Select(x => x.MessageHandlerType.GetBaseTypes().SingleOrDefault(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(SubscriptionHandler<>))?.GenericTypeArguments[0])
+                                                               .Where(x => x != null);
 
 
-            var messageContext = GetMessageContextForSubscriptionsUpdated();
-            foreach (var subscription in subscriptions)
-            {
-                var subscriptionsUpdated = new SubscriptionsUpdated(subscription, peerId);
-                var dispatch = new MessageDispatch(messageContext, subscriptionsUpdated, _serializer, GetOnLocalMessageDispatchedContinuation(null));
-                _messageDispatcher.Dispatch(dispatch);
-            }
-        }
+        _directory.EnableSubscriptionsUpdatedFor(snapshotGeneratingMessageTypes!);
+    }
 
 
-        private MessageContext GetMessageContextForSubscriptionsUpdated()
-            => MessageContext.Current ?? MessageContext.CreateOverride(PeerId, EndPoint);
+    private void DispatchSubscriptionsUpdatedMessages(PeerId peerId, IReadOnlyList<Subscription> subscriptions)
+    {
+        if (peerId == PeerId)
+            return;
 
 
-        private string DumpMessageOnDisk(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent)
+        var messageContext = GetMessageContextForSubscriptionsUpdated();
+        foreach (var subscription in subscriptions)
         {
         {
-            if (string.IsNullOrEmpty(DeserializationFailureDumpDirectoryPath))
-                return "Message could not be dumped";
+            var subscriptionsUpdated = new SubscriptionsUpdated(subscription, peerId);
+            var dispatch = new MessageDispatch(messageContext, subscriptionsUpdated, _serializer, GetOnLocalMessageDispatchedContinuation(null));
+            _messageDispatcher.Dispatch(dispatch);
+        }
+    }
 
 
-            try
-            {
-                if (!System.IO.Directory.Exists(DeserializationFailureDumpDirectoryPath))
-                    System.IO.Directory.CreateDirectory(DeserializationFailureDumpDirectoryPath);
+    private MessageContext GetMessageContextForSubscriptionsUpdated()
+        => MessageContext.Current ?? MessageContext.CreateOverride(PeerId, EndPoint);
 
 
-                var dumpFileName = $"{_deserializationFailureTimestampProvider.NextUtcTimestamp():yyyyMMdd-HH-mm-ss.fffffff}_{messageTypeId.FullName}";
-                var dumpFilePath = Path.Combine(DeserializationFailureDumpDirectoryPath, dumpFileName);
-                using (var fileStream = new FileStream(dumpFilePath, FileMode.Create))
-                {
-                    var messageBytes = messageContent.ToArray();
-                    fileStream.Write(messageBytes, 0, messageBytes.Length);
-                }
+    private string DumpMessageOnDisk(MessageTypeId messageTypeId, ReadOnlyMemory<byte> messageContent)
+    {
+        if (string.IsNullOrEmpty(DeserializationFailureDumpDirectoryPath))
+            return "Message could not be dumped";
 
 
-                return dumpFilePath;
-            }
-            catch (Exception ex)
+        try
+        {
+            if (!System.IO.Directory.Exists(DeserializationFailureDumpDirectoryPath))
+                System.IO.Directory.CreateDirectory(DeserializationFailureDumpDirectoryPath);
+
+            var dumpFileName = $"{_deserializationFailureTimestampProvider.NextUtcTimestamp():yyyyMMdd-HH-mm-ss.fffffff}_{messageTypeId.FullName}";
+            var dumpFilePath = Path.Combine(DeserializationFailureDumpDirectoryPath, dumpFileName);
+            using (var fileStream = new FileStream(dumpFilePath, FileMode.Create))
             {
             {
-                return "Message could not be dumped: " + ex;
+                var messageBytes = messageContent.ToArray();
+                fileStream.Write(messageBytes, 0, messageBytes.Length);
             }
             }
-        }
 
 
-        public void Dispose()
-        {
-            if (Status == BusStatus.Started)
-                Stop();
+            return dumpFilePath;
         }
         }
-
-        private enum BusStatus
+        catch (Exception ex)
         {
         {
-            Stopped,
-            Stopping,
-            Starting,
-            Started
+            return "Message could not be dumped: " + ex;
         }
         }
+    }
 
 
-        private class SubscriptionStatus
-        {
-            public int CurrentSubscriptionCount;
-            public int StartupSubscriptionCount;
+    public void Dispose()
+    {
+        if (Status == BusStatus.Started)
+            Stop();
+    }
 
 
-            public bool IsEmpty => CurrentSubscriptionCount <= 0 && StartupSubscriptionCount <= 0;
-        }
+    private enum BusStatus
+    {
+        Stopped,
+        Stopping,
+        Starting,
+        Started
+    }
+
+    private class SubscriptionStatus
+    {
+        public int CurrentSubscriptionCount;
+        public int StartupSubscriptionCount;
+
+        public bool IsEmpty => CurrentSubscriptionCount <= 0 && StartupSubscriptionCount <= 0;
     }
     }
 }
 }

+ 18 - 19
src/Abc.Zebus/Core/BusConfiguration.cs

@@ -1,27 +1,26 @@
 using System;
 using System;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class BusConfiguration : IBusConfiguration
 {
 {
-    public class BusConfiguration : IBusConfiguration
+    public BusConfiguration(string directoryServiceEndPoint)
+        : this(new[] { directoryServiceEndPoint })
     {
     {
-        public BusConfiguration(string directoryServiceEndPoint)
-            : this(new[] { directoryServiceEndPoint })
-        {
-        }
-
-        public BusConfiguration(string[] directoryServiceEndPoints)
-        {
-            DirectoryServiceEndPoints = directoryServiceEndPoints;
-        }
+    }
 
 
-        public string[] DirectoryServiceEndPoints { get; set; }
-        public TimeSpan RegistrationTimeout { get; set; } = 30.Seconds();
-        public TimeSpan FaultedDirectoryRetryDelay { get; set; } = 5.Minutes();
-        public TimeSpan StartReplayTimeout { get; set; } = 30.Seconds();
-        public bool IsPersistent { get; set; } = false;
-        public bool IsDirectoryPickedRandomly { get; set; } = true;
-        public bool IsErrorPublicationEnabled { get; set; } = false;
-        public int MessagesBatchSize { get; set; } = 100;
+    public BusConfiguration(string[] directoryServiceEndPoints)
+    {
+        DirectoryServiceEndPoints = directoryServiceEndPoints;
     }
     }
+
+    public string[] DirectoryServiceEndPoints { get; set; }
+    public TimeSpan RegistrationTimeout { get; set; } = 30.Seconds();
+    public TimeSpan FaultedDirectoryRetryDelay { get; set; } = 5.Minutes();
+    public TimeSpan StartReplayTimeout { get; set; } = 30.Seconds();
+    public bool IsPersistent { get; set; } = false;
+    public bool IsDirectoryPickedRandomly { get; set; } = true;
+    public bool IsErrorPublicationEnabled { get; set; } = false;
+    public int MessagesBatchSize { get; set; } = 100;
 }
 }

+ 131 - 134
src/Abc.Zebus/Core/BusFactory.cs

@@ -9,172 +9,169 @@ using Abc.Zebus.Transport;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using StructureMap;
 using StructureMap;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class BusFactory
 {
 {
-    public class BusFactory
+    private readonly List<Action<ConfigurationExpression>> _configurationActions = new();
+    private readonly ZmqTransportConfiguration _transportConfiguration = new();
+    private readonly List<ScanTarget> _scanTargets = new();
+    private IBusConfiguration? _configuration;
+    private string? _environment;
+    public PeerId PeerId { get; set; }
+
+    public BusFactory()
+        : this(new Container())
     {
     {
-        private readonly List<Action<ConfigurationExpression>> _configurationActions = new List<Action<ConfigurationExpression>>();
-        private readonly ZmqTransportConfiguration _transportConfiguration = new ZmqTransportConfiguration();
-        private readonly List<ScanTarget> _scanTargets = new List<ScanTarget>();
-        private IBusConfiguration? _configuration;
-        private string? _environment;
-        public PeerId PeerId { get; set; }
-
-        public BusFactory()
-            : this(new Container())
-        {
-        }
+    }
 
 
-        public BusFactory(IContainer container)
-        {
-            PeerId = new PeerId("Abc.Testing." + Guid.NewGuid());
+    public BusFactory(IContainer container)
+    {
+        PeerId = new PeerId("Abc.Testing." + Guid.NewGuid());
 
 
-            _scanTargets.Add(new ScanTarget(typeof(IBus).Assembly, null));
+        _scanTargets.Add(new ScanTarget(typeof(IBus).Assembly, null));
 
 
-            Container = container;
-        }
+        Container = container;
+    }
 
 
-        public IContainer Container { get; }
+    public IContainer Container { get; }
 
 
-        public BusFactory WithConfiguration(string directoryEndPoints, string environment)
-        {
-            var endpoints = directoryEndPoints.Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
-            return WithConfiguration(CreateBusConfiguration(endpoints), environment);
-        }
+    public BusFactory WithConfiguration(string directoryEndPoints, string environment)
+    {
+        var endpoints = directoryEndPoints.Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
+        return WithConfiguration(CreateBusConfiguration(endpoints), environment);
+    }
 
 
-        public BusFactory WithConfiguration(IBusConfiguration configuration, string environment)
-        {
-            _configuration = configuration;
-            _environment = environment;
-            return this;
-        }
+    public BusFactory WithConfiguration(IBusConfiguration configuration, string environment)
+    {
+        _configuration = configuration;
+        _environment = environment;
+        return this;
+    }
 
 
-        public BusFactory WithPeerId(string peerId)
+    public BusFactory WithPeerId(string peerId)
+    {
+        PeerId = new PeerId(peerId.Replace("*", Guid.NewGuid().ToString()));
+        return this;
+    }
+
+    public BusFactory WithHandlers(params Type[] handlers)
+    {
+        foreach (var handler in handlers)
         {
         {
-            PeerId = new PeerId(peerId.Replace("*", Guid.NewGuid().ToString()));
-            return this;
+            _scanTargets.Add(new ScanTarget(handler.Assembly, handler));
         }
         }
 
 
-        public BusFactory WithHandlers(params Type[] handlers)
-        {
-            foreach (var handler in handlers)
-            {
-                _scanTargets.Add(new ScanTarget(handler.Assembly, handler));
-            }
+        return this;
+    }
 
 
-            return this;
-        }
+    public BusFactory WithScan()
+    {
+        _scanTargets.Add(new ScanTarget(null, null));
+        return this;
+    }
 
 
-        public BusFactory WithScan()
+    public BusFactory WithScan(IEnumerable<Assembly> assemblies)
+    {
+        foreach (var assembly in assemblies)
         {
         {
-            _scanTargets.Add(new ScanTarget(null, null));
-            return this;
+            _scanTargets.Add(new ScanTarget(assembly, null));
         }
         }
 
 
-        public BusFactory WithScan(IEnumerable<Assembly> assemblies)
-        {
-            foreach (var assembly in assemblies)
-            {
-                _scanTargets.Add(new ScanTarget(assembly, null));
-            }
+        return this;
+    }
 
 
-            return this;
-        }
+    public BusFactory ConfigureContainer(Action<ConfigurationExpression> containerConfiguration)
+    {
+        _configurationActions.Add(containerConfiguration);
+        return this;
+    }
 
 
-        public BusFactory ConfigureContainer(Action<ConfigurationExpression> containerConfiguration)
-        {
-            _configurationActions.Add(containerConfiguration);
-            return this;
-        }
+    public IBus CreateAndStartBus()
+    {
+        var bus = CreateBus();
+        bus.Start();
 
 
-        public IBus CreateAndStartBus()
-        {
-            var bus = CreateBus();
-            bus.Start();
+        return bus;
+    }
 
 
-            return bus;
-        }
+    public IBus CreateBus()
+    {
+        if (_configuration == null || _environment == null)
+            throw new InvalidOperationException("The CreateBus() method was called with no configuration (Call .WithConfiguration(...) first)");
 
 
-        public IBus CreateBus()
+        Container.Configure(x => x.AddRegistry<ZebusRegistry>());
+        Container.Configure(x =>
         {
         {
-            if (_configuration == null || _environment == null)
-                throw new InvalidOperationException("The CreateBus() method was called with no configuration (Call .WithConfiguration(...) first)");
-
-            Container.Configure(x => x.AddRegistry<ZebusRegistry>());
-            Container.Configure(x =>
-            {
-                x.ForSingletonOf<IBusConfiguration>().Use(_configuration);
-                x.ForSingletonOf<IZmqTransportConfiguration>().Use(_transportConfiguration);
-                x.ForSingletonOf<IMessageDispatcher>().Use(
-                    "MessageDispatcher factory",
-                    ctx =>
-                    {
-                        var dispatcher = new MessageDispatcher(ctx.GetAllInstances<IMessageHandlerInvokerLoader>().ToArray(), ctx.GetInstance<IDispatchQueueFactory>());
-                        dispatcher.ConfigureHandlerFilter(assembly => _scanTargets.Any(scanTarget => scanTarget.Matches(assembly)));
-                        dispatcher.ConfigureAssemblyFilter(type => _scanTargets.Any(scanTarget => scanTarget.Matches(type)));
-
-                        return dispatcher;
-                    }
-                );
-            });
-
-            foreach (var configurationAction in _configurationActions)
-            {
-                Container.Configure(configurationAction);
-            }
-
-            var bus = Container.GetInstance<IBus>();
-            bus.Configure(PeerId, _environment);
-            return bus;
-        }
-
-        private static IBusConfiguration CreateBusConfiguration(string[] directoryServiceEndPoints)
+            x.ForSingletonOf<IBusConfiguration>().Use(_configuration);
+            x.ForSingletonOf<IZmqTransportConfiguration>().Use(_transportConfiguration);
+            x.ForSingletonOf<IMessageDispatcher>().Use(
+                "MessageDispatcher factory",
+                ctx =>
+                {
+                    var dispatcher = new MessageDispatcher(ctx.GetAllInstances<IMessageHandlerInvokerLoader>().ToArray(), ctx.GetInstance<IDispatchQueueFactory>());
+                    dispatcher.ConfigureHandlerFilter(assembly => _scanTargets.Any(scanTarget => scanTarget.Matches(assembly)));
+                    dispatcher.ConfigureAssemblyFilter(type => _scanTargets.Any(scanTarget => scanTarget.Matches(type)));
+
+                    return dispatcher;
+                }
+            );
+        });
+
+        foreach (var configurationAction in _configurationActions)
         {
         {
-            return new BusConfiguration(directoryServiceEndPoints)
-            {
-                IsPersistent = false,
-                RegistrationTimeout = 10.Seconds(),
-                StartReplayTimeout = 30.Seconds(),
-                IsDirectoryPickedRandomly = false,
-                IsErrorPublicationEnabled = false,
-            };
+            Container.Configure(configurationAction);
         }
         }
 
 
-        public BusFactory WithEndpoint(string endpoint)
+        var bus = Container.GetInstance<IBus>();
+        bus.Configure(PeerId, _environment);
+        return bus;
+    }
+
+    private static IBusConfiguration CreateBusConfiguration(string[] directoryServiceEndPoints)
+    {
+        return new BusConfiguration(directoryServiceEndPoints)
         {
         {
-            _transportConfiguration.InboundEndPoint = endpoint;
-            return this;
-        }
+            IsPersistent = false,
+            RegistrationTimeout = 10.Seconds(),
+            StartReplayTimeout = 30.Seconds(),
+            IsDirectoryPickedRandomly = false,
+            IsErrorPublicationEnabled = false,
+        };
+    }
 
 
-        public BusFactory WithWaitForEndOfStreamAckTimeout(TimeSpan timeout)
+    public BusFactory WithEndpoint(string endpoint)
+    {
+        _transportConfiguration.InboundEndPoint = endpoint;
+        return this;
+    }
+
+    public BusFactory WithWaitForEndOfStreamAckTimeout(TimeSpan timeout)
+    {
+        _transportConfiguration.WaitForEndOfStreamAckTimeout = timeout;
+        return this;
+    }
+
+    private class ScanTarget
+    {
+        private readonly Assembly? _assembly;
+        private readonly Type? _type;
+
+        public ScanTarget(Assembly? assembly, Type? type)
         {
         {
-            _transportConfiguration.WaitForEndOfStreamAckTimeout = timeout;
-            return this;
+            _assembly = assembly;
+            _type = type;
         }
         }
 
 
-        private class ScanTarget
+        public bool Matches(Assembly assembly)
+            => _assembly == null || _assembly == assembly;
+
+        public bool Matches(Type type)
         {
         {
-            private readonly Assembly? _assembly;
-            private readonly Type? _type;
-
-            public ScanTarget(Assembly? assembly, Type? type)
-            {
-                _assembly = assembly;
-                _type = type;
-            }
-
-            public bool Matches(Assembly assembly)
-            {
-                return (_assembly == null || _assembly == assembly);
-            }
-
-            public bool Matches(Type type)
-            {
-                if (_assembly == null)
-                    return true;
-
-                return type.Assembly == _assembly && (_type == null || _type == type);
-            }
+            if (_assembly == null)
+                return true;
+
+            return type.Assembly == _assembly && (_type == null || _type == type);
         }
         }
     }
     }
 }
 }

+ 133 - 134
src/Abc.Zebus/Core/BusMessageLogger.cs

@@ -8,183 +8,182 @@ using Abc.Zebus.Transport;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+internal class BusMessageLogger
 {
 {
-    internal class BusMessageLogger
-    {
-        private static readonly ConcurrentDictionary<Type, MessageTypeLogHelper> _logHelpers = new ConcurrentDictionary<Type, MessageTypeLogHelper>();
-        private readonly ILogger _logger;
-        private bool _logDebugEnabled;
-        private bool _logInfoEnabled;
+    private static readonly ConcurrentDictionary<Type, MessageTypeLogHelper> _logHelpers = new();
+    private readonly ILogger _logger;
+    private bool _logDebugEnabled;
+    private bool _logInfoEnabled;
 
 
-        public BusMessageLogger(Type loggerType)
-            : this(loggerType.FullName!)
-        {
-        }
+    public BusMessageLogger(Type loggerType)
+        : this(loggerType.FullName!)
+    {
+    }
 
 
-        public BusMessageLogger(string loggerFullName)
-        {
-            _logger = ZebusLogManager.GetLogger(loggerFullName);
+    public BusMessageLogger(string loggerFullName)
+    {
+        _logger = ZebusLogManager.GetLogger(loggerFullName);
 
 
-            // Instances of BusMessageLogger are static, no need to unsubscribe from this event
-            ZebusLogManager.ConfigurationUpdated += UpdateLogConfig;
+        // Instances of BusMessageLogger are static, no need to unsubscribe from this event
+        ZebusLogManager.ConfigurationUpdated += UpdateLogConfig;
 
 
-            UpdateLogConfig();
+        UpdateLogConfig();
 
 
-            void UpdateLogConfig()
-            {
-                _logDebugEnabled = _logger.IsEnabled(LogLevel.Debug);
-                _logInfoEnabled = _logger.IsEnabled(LogLevel.Information);
-            }
+        void UpdateLogConfig()
+        {
+            _logDebugEnabled = _logger.IsEnabled(LogLevel.Debug);
+            _logInfoEnabled = _logger.IsEnabled(LogLevel.Information);
         }
         }
+    }
 
 
-        public bool IsInfoEnabled(IMessage message)
-            => _logInfoEnabled && GetLogHelper(message).Logger.IsEnabled(LogLevel.Information);
+    public bool IsInfoEnabled(IMessage message)
+        => _logInfoEnabled && GetLogHelper(message).Logger.IsEnabled(LogLevel.Information);
 
 
-        public void LogHandleMessage(IList<IMessage> messages, string? dispatchQueueName, MessageId? messageId)
-        {
-            var message = messages[0];
+    public void LogHandleMessage(IList<IMessage> messages, string? dispatchQueueName, MessageId? messageId)
+    {
+        var message = messages[0];
 
 
-            if (!TryGetLogHelperForInfo(message, out var logHelper))
-                return;
+        if (!TryGetLogHelperForInfo(message, out var logHelper))
+            return;
 
 
-            var messageText = logHelper.GetMessageText(message);
-            var dispatchQueueNameText = HasCustomDispatchQueue() ? $" [{dispatchQueueName}]" : "";
-            var batchText = messages.Count > 1 ? $" Count: {messages.Count}" : "";
+        var messageText = logHelper.GetMessageText(message);
+        var dispatchQueueNameText = HasCustomDispatchQueue() ? $" [{dispatchQueueName}]" : "";
+        var batchText = messages.Count > 1 ? $" Count: {messages.Count}" : "";
 
 
-            _logger.LogInformation($"HANDLE{dispatchQueueNameText}: {messageText}{batchText} [{messageId}]");
+        _logger.LogInformation($"HANDLE{dispatchQueueNameText}: {messageText}{batchText} [{messageId}]");
 
 
-            bool HasCustomDispatchQueue() => !string.IsNullOrEmpty(dispatchQueueName) && dispatchQueueName != DispatchQueueNameScanner.DefaultQueueName;
-        }
+        bool HasCustomDispatchQueue() => !string.IsNullOrEmpty(dispatchQueueName) && dispatchQueueName != DispatchQueueNameScanner.DefaultQueueName;
+    }
 
 
-        public void LogReceiveMessageAck(MessageExecutionCompleted messageAck)
-        {
-            if (!TryGetLogHelperForDebug(messageAck, out _))
-                return;
+    public void LogReceiveMessageAck(MessageExecutionCompleted messageAck)
+    {
+        if (!TryGetLogHelperForDebug(messageAck, out _))
+            return;
 
 
-            _logger.LogDebug($"RECV ACK {{{messageAck}}}");
-        }
+        _logger.LogDebug($"RECV ACK {{{messageAck}}}");
+    }
 
 
-        public void LogReceiveMessageLocal(IMessage message)
-        {
-            if (!TryGetLogHelperForDebug(message, out var logHelper))
-                return;
+    public void LogReceiveMessageLocal(IMessage message)
+    {
+        if (!TryGetLogHelperForDebug(message, out var logHelper))
+            return;
 
 
-            var messageText = logHelper.GetMessageText(message);
-            _logger.LogDebug($"RECV local: {messageText}");
-        }
+        var messageText = logHelper.GetMessageText(message);
+        _logger.LogDebug($"RECV local: {messageText}");
+    }
 
 
-        public void LogReceiveMessageRemote(IMessage message, TransportMessage transportMessage)
-        {
-            if (!TryGetLogHelperForDebug(message, out var logHelper))
-                return;
+    public void LogReceiveMessageRemote(IMessage message, TransportMessage transportMessage)
+    {
+        if (!TryGetLogHelperForDebug(message, out var logHelper))
+            return;
 
 
-            var messageText = logHelper.GetMessageText(message);
-            _logger.LogDebug($"RECV remote: {messageText} from {transportMessage.SenderId} ({transportMessage.Content.Length} bytes). [{transportMessage.Id}]");
-        }
+        var messageText = logHelper.GetMessageText(message);
+        _logger.LogDebug($"RECV remote: {messageText} from {transportMessage.SenderId} ({transportMessage.Content.Length} bytes). [{transportMessage.Id}]");
+    }
 
 
-        public void LogSendMessage(IMessage message, IList<Peer> peers)
-        {
-            if (!TryGetLogHelperForInfo(message, out var logHelper))
-                return;
+    public void LogSendMessage(IMessage message, IList<Peer> peers)
+    {
+        if (!TryGetLogHelperForInfo(message, out var logHelper))
+            return;
 
 
-            var messageText = logHelper.GetMessageText(message);
-            var targetPeersText = GetTargetPeersText(peers);
+        var messageText = logHelper.GetMessageText(message);
+        var targetPeersText = GetTargetPeersText(peers);
 
 
-            _logger.LogInformation($"SEND: {messageText} to {targetPeersText}");
-        }
+        _logger.LogInformation($"SEND: {messageText} to {targetPeersText}");
+    }
 
 
-        public void LogSendMessage(IMessage message, IList<Peer> peers, TransportMessage transportMessage)
-        {
-            if (!TryGetLogHelperForInfo(message, out var logHelper))
-                return;
+    public void LogSendMessage(IMessage message, IList<Peer> peers, TransportMessage transportMessage)
+    {
+        if (!TryGetLogHelperForInfo(message, out var logHelper))
+            return;
 
 
-            var messageText = logHelper.GetMessageText(message);
-            var targetPeersText = GetTargetPeersText(peers);
+        var messageText = logHelper.GetMessageText(message);
+        var targetPeersText = GetTargetPeersText(peers);
 
 
-            _logger.LogInformation($"SEND: {messageText} to {targetPeersText} ({transportMessage.Content.Length} bytes) [{transportMessage.Id}]");
-        }
+        _logger.LogInformation($"SEND: {messageText} to {targetPeersText} ({transportMessage.Content.Length} bytes) [{transportMessage.Id}]");
+    }
 
 
-        private static string GetTargetPeersText(IList<Peer> peers)
+    private static string GetTargetPeersText(IList<Peer> peers)
+    {
+        switch (peers.Count)
         {
         {
-            switch (peers.Count)
-            {
-                case 0:
-                    return "no target peer";
-
-                case 1:
-                    return peers[0].Id.ToString();
-
-                default:
-                    var otherPeersCount = peers.Count - 1;
-                    return otherPeersCount > 1
-                        ? peers[0].Id + " and " + otherPeersCount + " other peers"
-                        : peers[0].Id + " and " + otherPeersCount + " other peer";
-            }
+            case 0:
+                return "no target peer";
+
+            case 1:
+                return peers[0].Id.ToString();
+
+            default:
+                var otherPeersCount = peers.Count - 1;
+                return otherPeersCount > 1
+                    ? peers[0].Id + " and " + otherPeersCount + " other peers"
+                    : peers[0].Id + " and " + otherPeersCount + " other peer";
         }
         }
+    }
 
 
-        public static string ToString(IMessage message)
-            => GetLogHelper(message).GetMessageText(message);
+    public static string ToString(IMessage message)
+        => GetLogHelper(message).GetMessageText(message);
 
 
-        [SuppressMessage("ReSharper", "ConvertClosureToMethodGroup")]
-        private static MessageTypeLogHelper GetLogHelper(IMessage message)
-            => _logHelpers.GetOrAdd(message.GetType(), type => CreateLogger(type));
+    [SuppressMessage("ReSharper", "ConvertClosureToMethodGroup")]
+    private static MessageTypeLogHelper GetLogHelper(IMessage message)
+        => _logHelpers.GetOrAdd(message.GetType(), type => CreateLogger(type));
 
 
-        private static MessageTypeLogHelper CreateLogger(Type messageType)
-        {
-            var logger = ZebusLogManager.GetLogger(messageType);
-            var hasToStringOverride = HasToStringOverride(messageType);
+    private static MessageTypeLogHelper CreateLogger(Type messageType)
+    {
+        var logger = ZebusLogManager.GetLogger(messageType);
+        var hasToStringOverride = HasToStringOverride(messageType);
 
 
-            return new MessageTypeLogHelper(logger, hasToStringOverride, messageType.GetPrettyName());
-        }
+        return new MessageTypeLogHelper(logger, hasToStringOverride, messageType.GetPrettyName());
+    }
 
 
-        private bool TryGetLogHelperForInfo(IMessage message, [NotNullWhen(true)] out MessageTypeLogHelper? logHelper)
+    private bool TryGetLogHelperForInfo(IMessage message, [NotNullWhen(true)] out MessageTypeLogHelper? logHelper)
+    {
+        if (!_logInfoEnabled)
         {
         {
-            if (!_logInfoEnabled)
-            {
-                logHelper = null;
-                return false;
-            }
-
-            logHelper = GetLogHelper(message);
-            return logHelper.Logger.IsEnabled(LogLevel.Information);
+            logHelper = null;
+            return false;
         }
         }
 
 
-        private bool TryGetLogHelperForDebug(IMessage message, [NotNullWhen(true)] out MessageTypeLogHelper? logHelper)
+        logHelper = GetLogHelper(message);
+        return logHelper.Logger.IsEnabled(LogLevel.Information);
+    }
+
+    private bool TryGetLogHelperForDebug(IMessage message, [NotNullWhen(true)] out MessageTypeLogHelper? logHelper)
+    {
+        if (!_logDebugEnabled)
         {
         {
-            if (!_logDebugEnabled)
-            {
-                logHelper = null;
-                return false;
-            }
-
-            logHelper = GetLogHelper(message);
-            return logHelper.Logger.IsEnabled(LogLevel.Debug);
+            logHelper = null;
+            return false;
         }
         }
 
 
-        private static bool HasToStringOverride(Type messageType)
+        logHelper = GetLogHelper(message);
+        return logHelper.Logger.IsEnabled(LogLevel.Debug);
+    }
+
+    private static bool HasToStringOverride(Type messageType)
+    {
+        var methodInfo = messageType.GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
+        return methodInfo != null;
+    }
+
+    private class MessageTypeLogHelper
+    {
+        public readonly ILogger Logger;
+        private readonly bool _hasToStringOverride;
+        private readonly string _messageTypeName;
+
+        public MessageTypeLogHelper(ILogger logger, bool hasToStringOverride, string messageTypeName)
         {
         {
-            var methodInfo = messageType.GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
-            return methodInfo != null;
+            Logger = logger;
+            _hasToStringOverride = hasToStringOverride;
+            _messageTypeName = messageTypeName;
         }
         }
 
 
-        private class MessageTypeLogHelper
+        public string GetMessageText(IMessage message)
         {
         {
-            public readonly ILogger Logger;
-            private readonly bool _hasToStringOverride;
-            private readonly string _messageTypeName;
-
-            public MessageTypeLogHelper(ILogger logger, bool hasToStringOverride, string messageTypeName)
-            {
-                Logger = logger;
-                _hasToStringOverride = hasToStringOverride;
-                _messageTypeName = messageTypeName;
-            }
-
-            public string GetMessageText(IMessage message)
-            {
-                return _hasToStringOverride ? $"{_messageTypeName} {{{message}}}" : $"{_messageTypeName}";
-            }
+            return _hasToStringOverride ? $"{_messageTypeName} {{{message}}}" : $"{_messageTypeName}";
         }
         }
     }
     }
 }
 }

+ 4 - 5
src/Abc.Zebus/Core/DefaultMessageSendingStrategy.cs

@@ -1,9 +1,8 @@
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class DefaultMessageSendingStrategy : IMessageSendingStrategy
 {
 {
-    public class DefaultMessageSendingStrategy : IMessageSendingStrategy
-    {
-        public bool IsMessagePersistent(TransportMessage transportMessage) => transportMessage.MessageTypeId.IsPersistent();
-    }
+    public bool IsMessagePersistent(TransportMessage transportMessage) => transportMessage.MessageTypeId.IsPersistent();
 }
 }

+ 7 - 8
src/Abc.Zebus/Core/DefaultStoppingStrategy.cs

@@ -1,14 +1,13 @@
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class DefaultStoppingStrategy : IStoppingStrategy
 {
 {
-    public class DefaultStoppingStrategy : IStoppingStrategy
+    public void Stop(ITransport transport, IMessageDispatcher messageDispatcher)
     {
     {
-        public void Stop(ITransport transport, IMessageDispatcher messageDispatcher)
-        {
-            messageDispatcher.Stop();
-            transport.Stop();   
-        }
+        messageDispatcher.Stop();
+        transport.Stop();
     }
     }
-}
+}

+ 4 - 5
src/Abc.Zebus/Core/IMessageSendingStrategy.cs

@@ -1,9 +1,8 @@
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public interface IMessageSendingStrategy
 {
 {
-    public interface IMessageSendingStrategy
-    {
-        bool IsMessagePersistent(TransportMessage transportMessage);
-    }
+    bool IsMessagePersistent(TransportMessage transportMessage);
 }
 }

+ 5 - 6
src/Abc.Zebus/Core/IStoppingStrategy.cs

@@ -1,10 +1,9 @@
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public interface IStoppingStrategy
 {
 {
-    public interface IStoppingStrategy
-    {
-        void Stop(ITransport transport, IMessageDispatcher messageDispatcher);
-    }
-}
+    void Stop(ITransport transport, IMessageDispatcher messageDispatcher);
+}

+ 73 - 74
src/Abc.Zebus/Core/MessageContextAwareBus.cs

@@ -1,103 +1,102 @@
 using System;
 using System;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using Abc.Zebus.Subscriptions;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+public class MessageContextAwareBus : IInternalBus
 {
 {
-    public class MessageContextAwareBus : IInternalBus
-    {
-        private readonly IBus _bus;
-        public readonly MessageContext MessageContext;
+    private readonly IBus _bus;
+    public readonly MessageContext MessageContext;
 
 
-        public MessageContextAwareBus(IBus bus, MessageContext messageContext)
-        {
-            _bus = bus;
-            MessageContext = messageContext;
-        }
+    public MessageContextAwareBus(IBus bus, MessageContext messageContext)
+    {
+        _bus = bus;
+        MessageContext = messageContext;
+    }
 
 
-        public IBus InnerBus => _bus;
-        public PeerId PeerId => _bus.PeerId;
-        public string Environment => _bus.Environment;
-        public bool IsRunning => _bus.IsRunning;
+    public IBus InnerBus => _bus;
+    public PeerId PeerId => _bus.PeerId;
+    public string Environment => _bus.Environment;
+    public bool IsRunning => _bus.IsRunning;
 
 
-        public void Configure(PeerId peerId, string environment) => _bus.Configure(peerId, environment);
+    public void Configure(PeerId peerId, string environment) => _bus.Configure(peerId, environment);
 
 
-        public void Publish(IEvent message)
+    public void Publish(IEvent message)
+    {
+        using (MessageContext.SetCurrent(MessageContext))
         {
         {
-            using (MessageContext.SetCurrent(MessageContext))
-            {
-                _bus.Publish(message);
-            }
+            _bus.Publish(message);
         }
         }
+    }
 
 
-        public Task<CommandResult> Send(ICommand message)
+    public Task<CommandResult> Send(ICommand message)
+    {
+        using (MessageContext.SetCurrent(MessageContext))
         {
         {
-            using (MessageContext.SetCurrent(MessageContext))
-            {
-                return _bus.Send(message);
-            }
+            return _bus.Send(message);
         }
         }
+    }
 
 
-        public Task<CommandResult> Send(ICommand message, Peer peer)
+    public Task<CommandResult> Send(ICommand message, Peer peer)
+    {
+        using (MessageContext.SetCurrent(MessageContext))
         {
         {
-            using (MessageContext.SetCurrent(MessageContext))
-            {
-                return _bus.Send(message, peer);
-            }
+            return _bus.Send(message, peer);
         }
         }
+    }
 
 
-        public Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
-            => _bus.SubscribeAsync(request);
+    public Task<IDisposable> SubscribeAsync(SubscriptionRequest request)
+        => _bus.SubscribeAsync(request);
 
 
-        public Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
-            => _bus.SubscribeAsync(request, handler);
+    public Task<IDisposable> SubscribeAsync(SubscriptionRequest request, Action<IMessage> handler)
+        => _bus.SubscribeAsync(request, handler);
 
 
-        public void Reply(int errorCode) => Reply(errorCode, null);
+    public void Reply(int errorCode) => Reply(errorCode, null);
 
 
-        public void Reply(int errorCode, string? message)
-        {
-            MessageContext.ReplyCode = errorCode;
-            MessageContext.ReplyMessage = message;
-        }
+    public void Reply(int errorCode, string? message)
+    {
+        MessageContext.ReplyCode = errorCode;
+        MessageContext.ReplyMessage = message;
+    }
 
 
-        public void Reply(IMessage? response)
-        {
-            MessageContext.ReplyResponse = response;
-        }
+    public void Reply(IMessage? response)
+    {
+        MessageContext.ReplyResponse = response;
+    }
 
 
-        public void Start() => _bus.Start();
-        public void Stop() => _bus.Stop();
+    public void Start() => _bus.Start();
+    public void Stop() => _bus.Stop();
 
 
-        public event Action? Starting
-        {
-            add => _bus.Starting += value;
-            remove => _bus.Starting -= value;
-        }
-
-        public event Action? Started
-        {
-            add => _bus.Started += value;
-            remove => _bus.Started -= value;
-        }
+    public event Action? Starting
+    {
+        add => _bus.Starting += value;
+        remove => _bus.Starting -= value;
+    }
 
 
-        public event Action? Stopping
-        {
-            add => _bus.Stopping += value;
-            remove => _bus.Stopping -= value;
-        }
+    public event Action? Started
+    {
+        add => _bus.Started += value;
+        remove => _bus.Started -= value;
+    }
 
 
-        public event Action? Stopped
-        {
-            add => _bus.Stopped += value;
-            remove => _bus.Stopped -= value;
-        }
+    public event Action? Stopping
+    {
+        add => _bus.Stopping += value;
+        remove => _bus.Stopping -= value;
+    }
 
 
-        public void Publish(IEvent message, PeerId targetPeer)
-        {
-            var internalBus = (IInternalBus)_bus;
-            internalBus.Publish(message, targetPeer);
-        }
+    public event Action? Stopped
+    {
+        add => _bus.Stopped += value;
+        remove => _bus.Stopped -= value;
+    }
 
 
-        public void Dispose() => _bus.Dispose();
+    public void Publish(IEvent message, PeerId targetPeer)
+    {
+        var internalBus = (IInternalBus)_bus;
+        internalBus.Publish(message, targetPeer);
     }
     }
+
+    public void Dispose()
+        => _bus.Dispose();
 }
 }

+ 79 - 81
src/Abc.Zebus/Core/MessageExecutionCompleted.cs

@@ -1,94 +1,92 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
-using System.IO;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Dispatch;
 using Abc.Zebus.Serialization;
 using Abc.Zebus.Serialization;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+[ProtoContract, Transient, Infrastructure]
+[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
+public class MessageExecutionCompleted : IMessage
 {
 {
-    [ProtoContract, Transient, Infrastructure]
-    [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
-    public class MessageExecutionCompleted : IMessage
+    public static readonly MessageTypeId TypeId = new(typeof(MessageExecutionCompleted));
+
+    [ProtoMember(1, IsRequired = true)]
+    public MessageId SourceCommandId { get; private set; }
+
+    [ProtoMember(2, IsRequired = true)]
+    public int ErrorCode { get; private set; }
+
+    [ProtoMember(3, IsRequired = false)]
+    public MessageTypeId? PayloadTypeId { get; private set; }
+
+    [ProtoMember(4, IsRequired = false)]
+    private byte[]? PayloadBytes
+    {
+        get => Payload.ToArray();
+        set => Payload = value;
+    }
+
+    [ProtoIgnore, JsonIgnore]
+    public ReadOnlyMemory<byte> Payload { get; private set; }
+
+    [ProtoMember(5, IsRequired = false)]
+    public string? ResponseMessage { get; private set; } = string.Empty;
+
+    [Obsolete("Use the constructor with the responseMessage parameter")]
+    public MessageExecutionCompleted(MessageId sourceCommandId, int errorCode)
+        : this(sourceCommandId, errorCode, null)
+    {
+    }
+
+    public MessageExecutionCompleted(MessageId sourceCommandId, int errorCode, string? responseMessage)
+    {
+        SourceCommandId = sourceCommandId;
+        ErrorCode = errorCode;
+        ResponseMessage = responseMessage ?? string.Empty;
+    }
+
+    public MessageExecutionCompleted(MessageId sourceCommandId, MessageTypeId payloadTypeId, ReadOnlyMemory<byte> payload)
     {
     {
-        public static readonly MessageTypeId TypeId = new MessageTypeId(typeof(MessageExecutionCompleted));
-
-        [ProtoMember(1, IsRequired = true)]
-        public MessageId SourceCommandId { get; private set; }
-
-        [ProtoMember(2, IsRequired = true)]
-        public int ErrorCode { get; private set; }
-
-        [ProtoMember(3, IsRequired = false)]
-        public MessageTypeId? PayloadTypeId { get; private set; }
-
-        [ProtoMember(4, IsRequired = false)]
-        private byte[]? PayloadBytes
-        {
-            get => Payload.ToArray();
-            set => Payload = value;
-        }
-
-        [ProtoIgnore, JsonIgnore]
-        public ReadOnlyMemory<byte> Payload { get; private set; }
-
-        [ProtoMember(5, IsRequired = false)]
-        public string? ResponseMessage { get; private set; } = string.Empty;
-
-        [Obsolete("Use the constructor with the responseMessage parameter")]
-        public MessageExecutionCompleted(MessageId sourceCommandId, int errorCode)
-            : this(sourceCommandId, errorCode, null)
-        {
-        }
-
-        public MessageExecutionCompleted(MessageId sourceCommandId, int errorCode, string? responseMessage)
-        {
-            SourceCommandId = sourceCommandId;
-            ErrorCode = errorCode;
-            ResponseMessage = responseMessage ?? string.Empty;
-        }
-
-        public MessageExecutionCompleted(MessageId sourceCommandId, MessageTypeId payloadTypeId, ReadOnlyMemory<byte> payload)
-        {
-            SourceCommandId = sourceCommandId;
-            ErrorCode = 0;
-            PayloadTypeId = payloadTypeId;
-            Payload = payload;
-        }
-
-        public override string ToString()
-        {
-            return ErrorCode == 0
-                ? $"CommandId: {SourceCommandId}"
-                : $"CommandId: {SourceCommandId}, ErrorCode: {ErrorCode} ({ResponseMessage})";
-        }
-
-        public static MessageExecutionCompleted Create(MessageContext messageContext, DispatchResult dispatchResult, IMessageSerializer serializer)
-        {
-            if (dispatchResult.Errors.Any())
-                return Failure(messageContext.MessageId, dispatchResult.Errors);
-
-            if (messageContext.ReplyResponse != null)
-                return Success(messageContext.MessageId, messageContext.ReplyResponse, serializer);
-
-            return new MessageExecutionCompleted(messageContext.MessageId, messageContext.ReplyCode, messageContext.ReplyMessage);
-        }
-
-        public static MessageExecutionCompleted Success(MessageId sourceCommandId, IMessage payload, IMessageSerializer serializer)
-        {
-            var payloadBytes = serializer.Serialize(payload);
-
-            return new MessageExecutionCompleted(sourceCommandId, payload.TypeId(), payloadBytes);
-        }
-
-        public static MessageExecutionCompleted Failure(MessageId sourceCommandId, IEnumerable<Exception> exceptions)
-        {
-            var errorStatus = CommandResult.GetErrorStatus(exceptions);
-
-            return new MessageExecutionCompleted(sourceCommandId, errorStatus.Code, errorStatus.Message);
-        }
+        SourceCommandId = sourceCommandId;
+        ErrorCode = 0;
+        PayloadTypeId = payloadTypeId;
+        Payload = payload;
+    }
+
+    public override string ToString()
+    {
+        return ErrorCode == 0
+            ? $"CommandId: {SourceCommandId}"
+            : $"CommandId: {SourceCommandId}, ErrorCode: {ErrorCode} ({ResponseMessage})";
+    }
+
+    public static MessageExecutionCompleted Create(MessageContext messageContext, DispatchResult dispatchResult, IMessageSerializer serializer)
+    {
+        if (dispatchResult.Errors.Any())
+            return Failure(messageContext.MessageId, dispatchResult.Errors);
+
+        if (messageContext.ReplyResponse != null)
+            return Success(messageContext.MessageId, messageContext.ReplyResponse, serializer);
+
+        return new MessageExecutionCompleted(messageContext.MessageId, messageContext.ReplyCode, messageContext.ReplyMessage);
+    }
+
+    public static MessageExecutionCompleted Success(MessageId sourceCommandId, IMessage payload, IMessageSerializer serializer)
+    {
+        var payloadBytes = serializer.Serialize(payload);
+
+        return new MessageExecutionCompleted(sourceCommandId, payload.TypeId(), payloadBytes);
+    }
+
+    public static MessageExecutionCompleted Failure(MessageId sourceCommandId, IEnumerable<Exception> exceptions)
+    {
+        var errorStatus = CommandResult.GetErrorStatus(exceptions);
+
+        return new MessageExecutionCompleted(sourceCommandId, errorStatus.Code, errorStatus.Message);
     }
     }
 }
 }

+ 18 - 19
src/Abc.Zebus/Core/RoundRobinPeerSelector.cs

@@ -2,33 +2,32 @@
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
-namespace Abc.Zebus.Core
+namespace Abc.Zebus.Core;
+
+internal class RoundRobinPeerSelector
 {
 {
-    internal class RoundRobinPeerSelector
-    {
-        private readonly ConcurrentDictionary<Type, int> _peerIndexes = new ConcurrentDictionary<Type, int>();
+    private readonly ConcurrentDictionary<Type, int> _peerIndexes = new();
 
 
-        public Peer? GetTargetPeer(ICommand command, IList<Peer> handlingPeers)
-        {
-            if (handlingPeers.Count == 1)
-                return handlingPeers[0];
+    public Peer? GetTargetPeer(ICommand command, IList<Peer> handlingPeers)
+    {
+        if (handlingPeers.Count == 1)
+            return handlingPeers[0];
 
 
-            if (handlingPeers.Count == 0)
-                return null;
+        if (handlingPeers.Count == 0)
+            return null;
 
 
-            var commandType = command.GetType();
+        var commandType = command.GetType();
 
 
-            if (!_peerIndexes.TryGetValue(commandType, out var index))
-                index = 0;
+        if (!_peerIndexes.TryGetValue(commandType, out var index))
+            index = 0;
 
 
-            if (index >= handlingPeers.Count)
-                index = 0;
+        if (index >= handlingPeers.Count)
+            index = 0;
 
 
-            var resolvedPeer = handlingPeers[index];
+        var resolvedPeer = handlingPeers[index];
 
 
-            _peerIndexes[commandType] = ++index;
+        _peerIndexes[commandType] = ++index;
 
 
-            return resolvedPeer;
-        }
+        return resolvedPeer;
     }
     }
 }
 }

+ 13 - 13
src/Abc.Zebus/Directory/DecommissionPeerCommand.cs

@@ -1,18 +1,18 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract]
-    public class DecommissionPeerCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        public DecommissionPeerCommand(PeerId peerId)
-        {
-            PeerId = peerId;
-        }
+[ProtoContract]
+public class DecommissionPeerCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public override string ToString() => PeerId.ToString();
+    public DecommissionPeerCommand(PeerId peerId)
+    {
+        PeerId = peerId;
     }
     }
-}
+
+    public override string ToString()
+        => PeerId.ToString();
+}

+ 5 - 6
src/Abc.Zebus/Directory/DirectoryErrorCodes.cs

@@ -1,7 +1,6 @@
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+public static class DirectoryErrorCodes
 {
 {
-    public static class DirectoryErrorCodes
-    {
-        public const int PeerAlreadyExists = 1001;
-    }
-}
+    public const int PeerAlreadyExists = 1001;
+}

+ 63 - 64
src/Abc.Zebus/Directory/DirectoryPeerSelector.cs

@@ -4,91 +4,90 @@ using System.Linq;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+internal class DirectoryPeerSelector
 {
 {
-    internal class DirectoryPeerSelector
-    {
-        private readonly Dictionary<string, EndPointState> _endPointStates = new(StringComparer.OrdinalIgnoreCase);
-        private readonly Random _random = new();
-        private readonly object _lock = new();
-        private readonly IBusConfiguration _configuration;
-        private string[]? _cachedEndPoints;
+    private readonly Dictionary<string, EndPointState> _endPointStates = new(StringComparer.OrdinalIgnoreCase);
+    private readonly Random _random = new();
+    private readonly object _lock = new();
+    private readonly IBusConfiguration _configuration;
+    private string[]? _cachedEndPoints;
 
 
-        public DirectoryPeerSelector(IBusConfiguration configuration)
-        {
-            _configuration = configuration;
-        }
+    public DirectoryPeerSelector(IBusConfiguration configuration)
+    {
+        _configuration = configuration;
+    }
 
 
-        public IEnumerable<Peer> GetPeers()
-        {
-            _cachedEndPoints = _configuration.DirectoryServiceEndPoints;
+    public IEnumerable<Peer> GetPeers()
+    {
+        _cachedEndPoints = _configuration.DirectoryServiceEndPoints;
 
 
-            return GetPeersImpl(_cachedEndPoints);
-        }
+        return GetPeersImpl(_cachedEndPoints);
+    }
 
 
-        public IEnumerable<Peer> GetPeersFromCache()
-        {
-            var endPoints = _cachedEndPoints ?? _configuration.DirectoryServiceEndPoints;
+    public IEnumerable<Peer> GetPeersFromCache()
+    {
+        var endPoints = _cachedEndPoints ?? _configuration.DirectoryServiceEndPoints;
 
 
-            return GetPeersImpl(endPoints);
-        }
+        return GetPeersImpl(endPoints);
+    }
 
 
-        private IEnumerable<Peer> GetPeersImpl(string[] endPoints)
-        {
-            var peerStates = GetPeerStates(endPoints);
+    private IEnumerable<Peer> GetPeersImpl(string[] endPoints)
+    {
+        var peerStates = GetPeerStates(endPoints);
 
 
-            return peerStates.OrderBy(x => x.IsFaulty).ThenBy(x => x.Priority).Select(x => x.Peer);
-        }
+        return peerStates.OrderBy(x => x.IsFaulty).ThenBy(x => x.Priority).Select(x => x.Peer);
+    }
 
 
-        private List<PeerState> GetPeerStates(string[] endPoints)
-        {
-            var isDirectoryPickedRandomly = _configuration.IsDirectoryPickedRandomly;
-            var faultedDirectoryRetryDelay = _configuration.FaultedDirectoryRetryDelay;
+    private List<PeerState> GetPeerStates(string[] endPoints)
+    {
+        var isDirectoryPickedRandomly = _configuration.IsDirectoryPickedRandomly;
+        var faultedDirectoryRetryDelay = _configuration.FaultedDirectoryRetryDelay;
 
 
-            var now = SystemDateTime.UtcNow;
-            var peerStates = new List<PeerState>();
+        var now = SystemDateTime.UtcNow;
+        var peerStates = new List<PeerState>();
 
 
-            lock (_lock)
+        lock (_lock)
+        {
+            foreach (var(endPoint, index) in endPoints.Select((x, index) => (x, index)))
             {
             {
-                foreach (var(endPoint, index) in endPoints.Select((x, index) => (x, index)))
-                {
-                    var endPointState = _endPointStates.GetValueOrAdd(endPoint, () => new EndPointState());
-                    var isFaulty = now < endPointState.ErrorTimestampUtc + faultedDirectoryRetryDelay;
-                    var priority = isDirectoryPickedRandomly ? _random.Next() : index;
-
-                    peerStates.Add(new PeerState(index, endPoint, isFaulty, priority));
-                }
+                var endPointState = _endPointStates.GetValueOrAdd(endPoint, () => new EndPointState());
+                var isFaulty = now < endPointState.ErrorTimestampUtc + faultedDirectoryRetryDelay;
+                var priority = isDirectoryPickedRandomly ? _random.Next() : index;
 
 
-                return peerStates;
+                peerStates.Add(new PeerState(index, endPoint, isFaulty, priority));
             }
             }
-        }
 
 
-        public void SetFaultedDirectory(Peer peer)
-        {
-            lock (_lock)
-            {
-                var endPointState = _endPointStates.GetValueOrAdd(peer.EndPoint, () => new EndPointState());
-                endPointState.ErrorTimestampUtc = SystemDateTime.UtcNow;
-            }
+            return peerStates;
         }
         }
+    }
 
 
-        private class EndPointState
+    public void SetFaultedDirectory(Peer peer)
+    {
+        lock (_lock)
         {
         {
-            public DateTime ErrorTimestampUtc { get; set; }
+            var endPointState = _endPointStates.GetValueOrAdd(peer.EndPoint, () => new EndPointState());
+            endPointState.ErrorTimestampUtc = SystemDateTime.UtcNow;
         }
         }
+    }
 
 
-        private class PeerState
-        {
-            public PeerState(int index, string endPoint, bool isFaulty, int priority)
-            {
-                Peer = new Peer(new PeerId($"Abc.Zebus.DirectoryService.{index}"), endPoint);
-                IsFaulty = isFaulty;
-                Priority = priority;
-            }
+    private class EndPointState
+    {
+        public DateTime ErrorTimestampUtc { get; set; }
+    }
 
 
-            public Peer Peer { get; }
-            public bool IsFaulty { get; }
-            public int Priority { get; }
+    private class PeerState
+    {
+        public PeerState(int index, string endPoint, bool isFaulty, int priority)
+        {
+            Peer = new Peer(new PeerId($"Abc.Zebus.DirectoryService.{index}"), endPoint);
+            IsFaulty = isFaulty;
+            Priority = priority;
         }
         }
+
+        public Peer Peer { get; }
+        public bool IsFaulty { get; }
+        public int Priority { get; }
     }
     }
 }
 }

+ 17 - 18
src/Abc.Zebus/Directory/IPeerDirectory.cs

@@ -2,29 +2,28 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+public interface IPeerDirectory
 {
 {
-    public interface IPeerDirectory
-    {
-        event Action<PeerId, PeerUpdateAction> PeerUpdated;
-        event Action<PeerId, IReadOnlyList<Subscription>> PeerSubscriptionsUpdated;
+    event Action<PeerId, PeerUpdateAction> PeerUpdated;
+    event Action<PeerId, IReadOnlyList<Subscription>> PeerSubscriptionsUpdated;
 
 
-        TimeSpan TimeSinceLastPing { get; }
+    TimeSpan TimeSinceLastPing { get; }
 
 
-        Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions);
-        Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes);
-        Task UnregisterAsync(IBus bus);
+    Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions);
+    Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes);
+    Task UnregisterAsync(IBus bus);
 
 
-        IList<Peer> GetPeersHandlingMessage(IMessage message);
-        IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding);
+    IList<Peer> GetPeersHandlingMessage(IMessage message);
+    IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding);
 
 
-        bool IsPersistent(PeerId peerId);
-        Peer? GetPeer(PeerId peerId);
-        void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types);
+    bool IsPersistent(PeerId peerId);
+    Peer? GetPeer(PeerId peerId);
+    void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types);
 
 
-        // TODO: move to a specific interface (IPeerDirectoryExplorer)
-        PeerDescriptor? GetPeerDescriptor(PeerId peerId);
+    // TODO: move to a specific interface (IPeerDirectoryExplorer)
+    PeerDescriptor? GetPeerDescriptor(PeerId peerId);
 
 
-        IEnumerable<PeerDescriptor> GetPeerDescriptors();
-    }
+    IEnumerable<PeerDescriptor> GetPeerDescriptors();
 }
 }

+ 16 - 16
src/Abc.Zebus/Directory/MarkPeerAsNotRespondingCommand.cs

@@ -1,23 +1,23 @@
 using System;
 using System;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract]
-    public class MarkPeerAsNotRespondingCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly DateTime TimestampUtc;
+[ProtoContract]
+public class MarkPeerAsNotRespondingCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public MarkPeerAsNotRespondingCommand(PeerId peerId, DateTime timestampUtc)
-        {
-            PeerId = peerId;
-            TimestampUtc = timestampUtc;
-        }
+    [ProtoMember(2, IsRequired = true)]
+    public readonly DateTime TimestampUtc;
 
 
-        public override string ToString() => PeerId.ToString();
+    public MarkPeerAsNotRespondingCommand(PeerId peerId, DateTime timestampUtc)
+    {
+        PeerId = peerId;
+        TimestampUtc = timestampUtc;
     }
     }
-}
+
+    public override string ToString()
+        => PeerId.ToString();
+}

+ 16 - 16
src/Abc.Zebus/Directory/MarkPeerAsRespondingCommand.cs

@@ -1,23 +1,23 @@
 using System;
 using System;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract]
-    public class MarkPeerAsRespondingCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly DateTime TimestampUtc;
+[ProtoContract]
+public class MarkPeerAsRespondingCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public MarkPeerAsRespondingCommand(PeerId peerId, DateTime timestampUtc)
-        {
-            PeerId = peerId;
-            TimestampUtc = timestampUtc;
-        }
+    [ProtoMember(2, IsRequired = true)]
+    public readonly DateTime TimestampUtc;
 
 
-        public override string ToString() => PeerId.ToString();
+    public MarkPeerAsRespondingCommand(PeerId peerId, DateTime timestampUtc)
+    {
+        PeerId = peerId;
+        TimestampUtc = timestampUtc;
     }
     }
-}
+
+    public override string ToString()
+        => PeerId.ToString();
+}

+ 30 - 32
src/Abc.Zebus/Directory/MessageBinding.cs

@@ -1,44 +1,42 @@
-using System;
-using Abc.Zebus.Routing;
+using Abc.Zebus.Routing;
 
 
-namespace Abc.Zebus.Directory
-{
-    public readonly struct MessageBinding
-    {
-        public readonly MessageTypeId MessageTypeId;
-        public readonly RoutingContent RoutingContent;
+namespace Abc.Zebus.Directory;
 
 
-        public MessageBinding(MessageTypeId messageTypeId, RoutingContent routingContent)
-        {
-            MessageTypeId = messageTypeId;
-            RoutingContent = routingContent;
-        }
+public readonly struct MessageBinding
+{
+    public readonly MessageTypeId MessageTypeId;
+    public readonly RoutingContent RoutingContent;
 
 
-        public static MessageBinding FromMessage(IMessage message)
-        {
-            var messageTypeId = message.TypeId();
-            var routingContent = GetRoutingContent(message, messageTypeId);
+    public MessageBinding(MessageTypeId messageTypeId, RoutingContent routingContent)
+    {
+        MessageTypeId = messageTypeId;
+        RoutingContent = routingContent;
+    }
 
 
-            return new MessageBinding(messageTypeId, routingContent);
-        }
+    public static MessageBinding FromMessage(IMessage message)
+    {
+        var messageTypeId = message.TypeId();
+        var routingContent = GetRoutingContent(message, messageTypeId);
 
 
-        public static MessageBinding Default<T>()
-            where T : IMessage => new(MessageUtil.TypeId<T>(), RoutingContent.Empty);
+        return new MessageBinding(messageTypeId, routingContent);
+    }
 
 
-        private static RoutingContent GetRoutingContent(IMessage message, MessageTypeId messageTypeId)
-        {
-            var members = messageTypeId.Descriptor.RoutingMembers;
-            if (members.Length == 0)
-                return RoutingContent.Empty;
+    public static MessageBinding Default<T>()
+        where T : IMessage => new(MessageUtil.TypeId<T>(), RoutingContent.Empty);
 
 
-            var values = new RoutingContentValue[members.Length];
+    private static RoutingContent GetRoutingContent(IMessage message, MessageTypeId messageTypeId)
+    {
+        var members = messageTypeId.Descriptor.RoutingMembers;
+        if (members.Length == 0)
+            return RoutingContent.Empty;
 
 
-            for (var tokenIndex = 0; tokenIndex < values.Length; ++tokenIndex)
-            {
-                values[tokenIndex] = members[tokenIndex].GetValue(message);
-            }
+        var values = new RoutingContentValue[members.Length];
 
 
-            return new RoutingContent(values);
+        for (var tokenIndex = 0; tokenIndex < values.Length; ++tokenIndex)
+        {
+            values[tokenIndex] = members[tokenIndex].GetValue(message);
         }
         }
+
+        return new RoutingContent(values);
     }
     }
 }
 }

+ 13 - 13
src/Abc.Zebus/Directory/PeerDecommissioned.cs

@@ -1,18 +1,18 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class PeerDecommissioned : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        public PeerDecommissioned(PeerId peerId)
-        {
-            PeerId = peerId;
-        }
+[ProtoContract, Transient]
+public class PeerDecommissioned : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public override string ToString() => PeerId.ToString();
+    public PeerDecommissioned(PeerId peerId)
+    {
+        PeerId = peerId;
     }
     }
-}
+
+    public override string ToString()
+        => PeerId.ToString();
+}

+ 45 - 45
src/Abc.Zebus/Directory/PeerDescriptor.cs

@@ -3,52 +3,52 @@ using System.Linq;
 using JetBrains.Annotations;
 using JetBrains.Annotations;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+[ProtoContract]
+public class PeerDescriptor
 {
 {
-    [ProtoContract]
-    public class PeerDescriptor
+    [ProtoMember(1, IsRequired = true)]
+    public readonly Peer Peer;
+
+    [ProtoMember(2, IsRequired = true)]
+    public Subscription[] Subscriptions { get; set; }
+
+    [ProtoMember(3, IsRequired = true)]
+    public bool IsPersistent { get; set; }
+
+    [ProtoMember(4, IsRequired = false)]
+    public DateTime? TimestampUtc { get; set; }
+
+    [ProtoMember(5, IsRequired = false)]
+    public bool HasDebuggerAttached { get; set; }
+
+    public PeerDescriptor(PeerId id, string endPoint, bool isPersistent, bool isUp, bool isResponding, DateTime timestampUtc, params Subscription[] subscriptions)
+    {
+        Peer = new Peer(id, endPoint, isUp, isResponding);
+        Subscriptions = subscriptions;
+        IsPersistent = isPersistent;
+        TimestampUtc = timestampUtc;
+    }
+
+    internal PeerDescriptor(PeerDescriptor other)
     {
     {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly Peer Peer;
-
-        [ProtoMember(2, IsRequired = true)]
-        public Subscription[] Subscriptions { get; set; }
-
-        [ProtoMember(3, IsRequired = true)]
-        public bool IsPersistent { get; set; }
-
-        [ProtoMember(4, IsRequired = false)]
-        public DateTime? TimestampUtc { get; set; }
-
-        [ProtoMember(5, IsRequired = false)]
-        public bool HasDebuggerAttached { get; set; }
-
-        public PeerDescriptor(PeerId id, string endPoint, bool isPersistent, bool isUp, bool isResponding, DateTime timestampUtc, params Subscription[] subscriptions)
-        {
-            Peer = new Peer(id, endPoint, isUp, isResponding);
-            Subscriptions = subscriptions;
-            IsPersistent = isPersistent;
-            TimestampUtc = timestampUtc;
-        }
-
-        internal PeerDescriptor(PeerDescriptor other)
-        {
-            Peer = new Peer(other.Peer);
-            Subscriptions = other.Subscriptions?.ToArray() ?? Array.Empty<Subscription>();
-            IsPersistent = other.IsPersistent;
-            TimestampUtc = other.TimestampUtc;
-            HasDebuggerAttached = other.HasDebuggerAttached;
-        }
-
-        [UsedImplicitly]
-        private PeerDescriptor()
-        {
-            Peer = default!;
-            Subscriptions = Array.Empty<Subscription>();
-        }
-
-        public PeerId PeerId => Peer.Id;
-
-        public override string ToString() => Peer.ToString();
+        Peer = new Peer(other.Peer);
+        Subscriptions = other.Subscriptions?.ToArray() ?? Array.Empty<Subscription>();
+        IsPersistent = other.IsPersistent;
+        TimestampUtc = other.TimestampUtc;
+        HasDebuggerAttached = other.HasDebuggerAttached;
     }
     }
+
+    [UsedImplicitly]
+    private PeerDescriptor()
+    {
+        Peer = default!;
+        Subscriptions = Array.Empty<Subscription>();
+    }
+
+    public PeerId PeerId => Peer.Id;
+
+    public override string ToString()
+        => Peer.ToString();
 }
 }

+ 94 - 95
src/Abc.Zebus/Directory/PeerDirectoryClient.PeerEntry.cs

@@ -5,143 +5,142 @@ using System.Linq;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+public partial class PeerDirectoryClient
 {
 {
-    public partial class PeerDirectoryClient
+    private class PeerEntry
     {
     {
-        private class PeerEntry
-        {
-            private readonly Dictionary<MessageTypeId, MessageTypeEntry> _peerSubscriptionsByMessageType = new Dictionary<MessageTypeId, MessageTypeEntry>();
-            private readonly ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> _globalSubscriptionsIndex;
+        private readonly Dictionary<MessageTypeId, MessageTypeEntry> _peerSubscriptionsByMessageType = new();
+        private readonly ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> _globalSubscriptionsIndex;
 
 
-            public PeerEntry(PeerDescriptor descriptor, ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> globalSubscriptionsIndex)
-            {
-                Peer = new Peer(descriptor.Peer);
-                IsPersistent = descriptor.IsPersistent;
-                TimestampUtc = descriptor.TimestampUtc ?? DateTime.UtcNow;
-                HasDebuggerAttached = descriptor.HasDebuggerAttached;
+        public PeerEntry(PeerDescriptor descriptor, ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> globalSubscriptionsIndex)
+        {
+            Peer = new Peer(descriptor.Peer);
+            IsPersistent = descriptor.IsPersistent;
+            TimestampUtc = descriptor.TimestampUtc ?? DateTime.UtcNow;
+            HasDebuggerAttached = descriptor.HasDebuggerAttached;
 
 
-                _globalSubscriptionsIndex = globalSubscriptionsIndex;
-            }
+            _globalSubscriptionsIndex = globalSubscriptionsIndex;
+        }
 
 
-            public Peer Peer { get; }
-            public bool IsPersistent { get; set; }
-            public DateTime TimestampUtc { get; set; }
-            public bool HasDebuggerAttached { get; set; }
+        public Peer Peer { get; }
+        public bool IsPersistent { get; set; }
+        public DateTime TimestampUtc { get; set; }
+        public bool HasDebuggerAttached { get; set; }
 
 
-            public PeerDescriptor ToPeerDescriptor()
+        public PeerDescriptor ToPeerDescriptor()
+        {
+            lock (_peerSubscriptionsByMessageType)
             {
             {
-                lock (_peerSubscriptionsByMessageType)
-                {
-                    var subscriptions = _peerSubscriptionsByMessageType.SelectMany(x => x.Value.BindingKeys.Select(bk => new Subscription(x.Key, bk)))
-                                                             .Distinct()
-                                                             .ToArray();
+                var subscriptions = _peerSubscriptionsByMessageType.SelectMany(x => x.Value.BindingKeys.Select(bk => new Subscription(x.Key, bk)))
+                                                                   .Distinct()
+                                                                   .ToArray();
 
 
-                    return new PeerDescriptor(Peer.Id, Peer.EndPoint, IsPersistent, Peer.IsUp, Peer.IsResponding, TimestampUtc, subscriptions);
-                }
+                return new PeerDescriptor(Peer.Id, Peer.EndPoint, IsPersistent, Peer.IsUp, Peer.IsResponding, TimestampUtc, subscriptions);
             }
             }
+        }
 
 
-            public void SetSubscriptions(IEnumerable<Subscription> subscriptions, DateTime? timestampUtc)
+        public void SetSubscriptions(IEnumerable<Subscription> subscriptions, DateTime? timestampUtc)
+        {
+            lock (_peerSubscriptionsByMessageType)
             {
             {
-                lock (_peerSubscriptionsByMessageType)
-                {
-                    var newBindingKeysByMessageType = subscriptions.GroupBy(x => x.MessageTypeId).ToDictionary(g => g.Key, g => g.Select(x => x.BindingKey));
+                var newBindingKeysByMessageType = subscriptions.GroupBy(x => x.MessageTypeId).ToDictionary(g => g.Key, g => g.Select(x => x.BindingKey));
 
 
-                    foreach (var messageSubscriptions in _peerSubscriptionsByMessageType)
-                    {
-                        if (!newBindingKeysByMessageType.ContainsKey(messageSubscriptions.Key))
-                            SetSubscriptionsForType(messageSubscriptions.Key, Enumerable.Empty<BindingKey>(), timestampUtc);
-                    }
+                foreach (var messageSubscriptions in _peerSubscriptionsByMessageType)
+                {
+                    if (!newBindingKeysByMessageType.ContainsKey(messageSubscriptions.Key))
+                        SetSubscriptionsForType(messageSubscriptions.Key, Enumerable.Empty<BindingKey>(), timestampUtc);
+                }
 
 
-                    foreach (var newBindingKeys in newBindingKeysByMessageType)
-                    {
-                        SetSubscriptionsForType(newBindingKeys.Key, newBindingKeys.Value, timestampUtc);
-                    }
+                foreach (var newBindingKeys in newBindingKeysByMessageType)
+                {
+                    SetSubscriptionsForType(newBindingKeys.Key, newBindingKeys.Value, timestampUtc);
                 }
                 }
             }
             }
+        }
 
 
-            public void SetSubscriptionsForType(IEnumerable<SubscriptionsForType> subscriptionsForTypes, DateTime? timestampUtc)
+        public void SetSubscriptionsForType(IEnumerable<SubscriptionsForType> subscriptionsForTypes, DateTime? timestampUtc)
+        {
+            lock (_peerSubscriptionsByMessageType)
             {
             {
-                lock (_peerSubscriptionsByMessageType)
+                foreach (var subscriptionsForType in subscriptionsForTypes)
                 {
                 {
-                    foreach (var subscriptionsForType in subscriptionsForTypes)
-                    {
-                        var messageTypeId = subscriptionsForType.MessageTypeId;
-                        var bindingKeys = subscriptionsForType.BindingKeys ?? Enumerable.Empty<BindingKey>();
-                        SetSubscriptionsForType(messageTypeId, bindingKeys, timestampUtc);
-                    }
+                    var messageTypeId = subscriptionsForType.MessageTypeId;
+                    var bindingKeys = subscriptionsForType.BindingKeys ?? Enumerable.Empty<BindingKey>();
+                    SetSubscriptionsForType(messageTypeId, bindingKeys, timestampUtc);
                 }
                 }
             }
             }
+        }
 
 
-            private void SetSubscriptionsForType(MessageTypeId messageTypeId, IEnumerable<BindingKey> bindingKeys, DateTime? timestampUtc)
-            {
-                var newBindingKeys = bindingKeys.ToHashSet();
+        private void SetSubscriptionsForType(MessageTypeId messageTypeId, IEnumerable<BindingKey> bindingKeys, DateTime? timestampUtc)
+        {
+            var newBindingKeys = bindingKeys.ToHashSet();
 
 
-                var messageTypeEntry = _peerSubscriptionsByMessageType.GetValueOrAdd(messageTypeId, MessageTypeEntry.Create);
-                if (messageTypeEntry.TimestampUtc > timestampUtc)
-                    return;
+            var messageTypeEntry = _peerSubscriptionsByMessageType.GetValueOrAdd(messageTypeId, MessageTypeEntry.Create);
+            if (messageTypeEntry.TimestampUtc > timestampUtc)
+                return;
 
 
-                messageTypeEntry.TimestampUtc = timestampUtc;
+            messageTypeEntry.TimestampUtc = timestampUtc;
 
 
-                foreach (var previousBindingKey in messageTypeEntry.BindingKeys.ToList())
-                {
-                    if (newBindingKeys.Remove(previousBindingKey))
-                        continue;
+            foreach (var previousBindingKey in messageTypeEntry.BindingKeys.ToList())
+            {
+                if (newBindingKeys.Remove(previousBindingKey))
+                    continue;
 
 
-                    messageTypeEntry.BindingKeys.Remove(previousBindingKey);
+                messageTypeEntry.BindingKeys.Remove(previousBindingKey);
 
 
-                    RemoveFromGlobalSubscriptionsIndex(messageTypeId, previousBindingKey);
-                }
+                RemoveFromGlobalSubscriptionsIndex(messageTypeId, previousBindingKey);
+            }
 
 
-                foreach (var newBindingKey in newBindingKeys)
-                {
-                    if (!messageTypeEntry.BindingKeys.Add(newBindingKey))
-                        continue;
+            foreach (var newBindingKey in newBindingKeys)
+            {
+                if (!messageTypeEntry.BindingKeys.Add(newBindingKey))
+                    continue;
 
 
-                    AddToGlobalSubscriptionsIndex(messageTypeId, newBindingKey);
-                }
+                AddToGlobalSubscriptionsIndex(messageTypeId, newBindingKey);
             }
             }
+        }
 
 
-            public void RemoveSubscriptions()
+        public void RemoveSubscriptions()
+        {
+            lock (_peerSubscriptionsByMessageType)
             {
             {
-                lock (_peerSubscriptionsByMessageType)
+                foreach (var messageSubscriptions in _peerSubscriptionsByMessageType)
                 {
                 {
-                    foreach (var messageSubscriptions in _peerSubscriptionsByMessageType)
+                    foreach (var bindingKey in messageSubscriptions.Value.BindingKeys)
                     {
                     {
-                        foreach (var bindingKey in messageSubscriptions.Value.BindingKeys)
-                        {
-                            RemoveFromGlobalSubscriptionsIndex(messageSubscriptions.Key, bindingKey);
-                        }
+                        RemoveFromGlobalSubscriptionsIndex(messageSubscriptions.Key, bindingKey);
                     }
                     }
-                    _peerSubscriptionsByMessageType.Clear();
                 }
                 }
+                _peerSubscriptionsByMessageType.Clear();
             }
             }
+        }
 
 
-            private void AddToGlobalSubscriptionsIndex(MessageTypeId messageTypeId, BindingKey bindingKey)
-            {
-                var subscriptionTree = _globalSubscriptionsIndex.GetOrAdd(messageTypeId, _ => new PeerSubscriptionTree());
-                subscriptionTree.Add(Peer, bindingKey);
-            }
+        private void AddToGlobalSubscriptionsIndex(MessageTypeId messageTypeId, BindingKey bindingKey)
+        {
+            var subscriptionTree = _globalSubscriptionsIndex.GetOrAdd(messageTypeId, _ => new PeerSubscriptionTree());
+            subscriptionTree.Add(Peer, bindingKey);
+        }
 
 
-            private void RemoveFromGlobalSubscriptionsIndex(MessageTypeId messageTypeId, BindingKey bindingKey)
-            {
-                var subscriptionTree = _globalSubscriptionsIndex.GetValueOrDefault(messageTypeId);
-                if (subscriptionTree == null)
-                    return;
+        private void RemoveFromGlobalSubscriptionsIndex(MessageTypeId messageTypeId, BindingKey bindingKey)
+        {
+            var subscriptionTree = _globalSubscriptionsIndex.GetValueOrDefault(messageTypeId);
+            if (subscriptionTree == null)
+                return;
 
 
-                subscriptionTree.Remove(Peer, bindingKey);
+            subscriptionTree.Remove(Peer, bindingKey);
 
 
-                if (subscriptionTree.IsEmpty)
-                    _globalSubscriptionsIndex.Remove(messageTypeId);
-            }
+            if (subscriptionTree.IsEmpty)
+                _globalSubscriptionsIndex.Remove(messageTypeId);
+        }
 
 
-            private class MessageTypeEntry
-            {
-                public static readonly Func<MessageTypeEntry> Create = () => new MessageTypeEntry();
+        private class MessageTypeEntry
+        {
+            public static readonly Func<MessageTypeEntry> Create = () => new MessageTypeEntry();
 
 
-                public readonly HashSet<BindingKey> BindingKeys = new HashSet<BindingKey>();
-                public DateTime? TimestampUtc;
-            }
+            public readonly HashSet<BindingKey> BindingKeys = new HashSet<BindingKey>();
+            public DateTime? TimestampUtc;
         }
         }
     }
     }
 }
 }

+ 369 - 370
src/Abc.Zebus/Directory/PeerDirectoryClient.cs

@@ -8,483 +8,482 @@ using Abc.Zebus.Util;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+public partial class PeerDirectoryClient : IPeerDirectory,
+                                           IMessageHandler<PeerStarted>,
+                                           IMessageHandler<PeerStopped>,
+                                           IMessageHandler<PeerDecommissioned>,
+                                           IMessageHandler<PingPeerCommand>,
+                                           IMessageHandler<PeerSubscriptionsUpdated>,
+                                           IMessageHandler<PeerSubscriptionsForTypesUpdated>,
+                                           IMessageHandler<PeerNotResponding>,
+                                           IMessageHandler<PeerResponding>
 {
 {
-    public partial class PeerDirectoryClient : IPeerDirectory,
-                                               IMessageHandler<PeerStarted>,
-                                               IMessageHandler<PeerStopped>,
-                                               IMessageHandler<PeerDecommissioned>,
-                                               IMessageHandler<PingPeerCommand>,
-                                               IMessageHandler<PeerSubscriptionsUpdated>,
-                                               IMessageHandler<PeerSubscriptionsForTypesUpdated>,
-                                               IMessageHandler<PeerNotResponding>,
-                                               IMessageHandler<PeerResponding>
+    private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(PeerDirectoryClient));
+
+    private readonly ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> _globalSubscriptionsIndex = new();
+    private readonly ConcurrentDictionary<PeerId, PeerEntry> _peers = new();
+    private readonly UniqueTimestampProvider _timestampProvider = new(10);
+    private readonly IBusConfiguration _configuration;
+    private readonly Stopwatch _pingStopwatch = new();
+    private readonly DirectoryPeerSelector _directorySelector;
+    private BlockingCollection<IEvent> _messagesReceivedDuringRegister;
+    private Peer _self = default!;
+    private volatile HashSet<Type> _observedSubscriptionMessageTypes = new();
+
+    public PeerDirectoryClient(IBusConfiguration configuration)
     {
     {
-        private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(PeerDirectoryClient));
-
-        private readonly ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree> _globalSubscriptionsIndex = new ConcurrentDictionary<MessageTypeId, PeerSubscriptionTree>();
-        private readonly ConcurrentDictionary<PeerId, PeerEntry> _peers = new ConcurrentDictionary<PeerId, PeerEntry>();
-        private readonly UniqueTimestampProvider _timestampProvider = new UniqueTimestampProvider(10);
-        private readonly IBusConfiguration _configuration;
-        private readonly Stopwatch _pingStopwatch = new Stopwatch();
-        private readonly DirectoryPeerSelector _directorySelector;
-        private BlockingCollection<IEvent> _messagesReceivedDuringRegister;
-        private Peer _self = default!;
-        private volatile HashSet<Type> _observedSubscriptionMessageTypes = new HashSet<Type>();
-
-        public PeerDirectoryClient(IBusConfiguration configuration)
-        {
-            _configuration = configuration;
-            _directorySelector = new DirectoryPeerSelector(configuration);
-
-            _messagesReceivedDuringRegister = new BlockingCollection<IEvent>();
-            _messagesReceivedDuringRegister.CompleteAdding();
-        }
+        _configuration = configuration;
+        _directorySelector = new DirectoryPeerSelector(configuration);
 
 
-        public event Action<PeerId, PeerUpdateAction>? PeerUpdated;
-        public event Action<PeerId, IReadOnlyList<Subscription>>? PeerSubscriptionsUpdated;
+        _messagesReceivedDuringRegister = new BlockingCollection<IEvent>();
+        _messagesReceivedDuringRegister.CompleteAdding();
+    }
 
 
-        public TimeSpan TimeSinceLastPing => _pingStopwatch.IsRunning ? _pingStopwatch.Elapsed : TimeSpan.MaxValue;
+    public event Action<PeerId, PeerUpdateAction>? PeerUpdated;
+    public event Action<PeerId, IReadOnlyList<Subscription>>? PeerSubscriptionsUpdated;
 
 
-        public async Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions)
-        {
-            _self = self;
+    public TimeSpan TimeSinceLastPing => _pingStopwatch.IsRunning ? _pingStopwatch.Elapsed : TimeSpan.MaxValue;
 
 
-            _globalSubscriptionsIndex.Clear();
-            _peers.Clear();
+    public async Task RegisterAsync(IBus bus, Peer self, IEnumerable<Subscription> subscriptions)
+    {
+        _self = self;
 
 
-            var selfDescriptor = CreateSelfDescriptor(subscriptions);
-            AddOrUpdatePeerEntry(selfDescriptor, shouldRaisePeerUpdated: false);
+        _globalSubscriptionsIndex.Clear();
+        _peers.Clear();
 
 
-            _messagesReceivedDuringRegister = new BlockingCollection<IEvent>();
+        var selfDescriptor = CreateSelfDescriptor(subscriptions);
+        AddOrUpdatePeerEntry(selfDescriptor, shouldRaisePeerUpdated: false);
 
 
-            try
-            {
-                await TryRegisterOnDirectoryAsync(bus, selfDescriptor).ConfigureAwait(false);
-            }
-            finally
-            {
-                _messagesReceivedDuringRegister.CompleteAdding();
-            }
+        _messagesReceivedDuringRegister = new BlockingCollection<IEvent>();
 
 
-            _pingStopwatch.Restart();
-            ProcessMessagesReceivedDuringRegister();
+        try
+        {
+            await TryRegisterOnDirectoryAsync(bus, selfDescriptor).ConfigureAwait(false);
+        }
+        finally
+        {
+            _messagesReceivedDuringRegister.CompleteAdding();
         }
         }
 
 
-        private void ProcessMessagesReceivedDuringRegister()
+        _pingStopwatch.Restart();
+        ProcessMessagesReceivedDuringRegister();
+    }
+
+    private void ProcessMessagesReceivedDuringRegister()
+    {
+        foreach (var message in _messagesReceivedDuringRegister.GetConsumingEnumerable())
         {
         {
-            foreach (var message in _messagesReceivedDuringRegister.GetConsumingEnumerable())
+            try
             {
             {
-                try
+                switch (message)
                 {
                 {
-                    switch (message)
-                    {
-                        case PeerStarted msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerStopped msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerDecommissioned msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerSubscriptionsUpdated msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerSubscriptionsForTypesUpdated msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerNotResponding msg:
-                            Handle(msg);
-                            break;
-
-                        case PeerResponding msg:
-                            Handle(msg);
-                            break;
-                    }
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogWarning(ex, $"Unable to process message {message.GetType()} {{{message}}}");
+                    case PeerStarted msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerStopped msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerDecommissioned msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerSubscriptionsUpdated msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerSubscriptionsForTypesUpdated msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerNotResponding msg:
+                        Handle(msg);
+                        break;
+
+                    case PeerResponding msg:
+                        Handle(msg);
+                        break;
                 }
                 }
             }
             }
-        }
-
-        private PeerDescriptor CreateSelfDescriptor(IEnumerable<Subscription> subscriptions)
-        {
-            return new PeerDescriptor(_self.Id, _self.EndPoint, _configuration.IsPersistent, true, true, _timestampProvider.NextUtcTimestamp(), subscriptions.ToArray())
+            catch (Exception ex)
             {
             {
-                HasDebuggerAttached = Debugger.IsAttached
-            };
+                _logger.LogWarning(ex, $"Unable to process message {message.GetType()} {{{message}}}");
+            }
         }
         }
+    }
 
 
-        private async Task TryRegisterOnDirectoryAsync(IBus bus, PeerDescriptor self)
+    private PeerDescriptor CreateSelfDescriptor(IEnumerable<Subscription> subscriptions)
+    {
+        return new PeerDescriptor(_self.Id, _self.EndPoint, _configuration.IsPersistent, true, true, _timestampProvider.NextUtcTimestamp(), subscriptions.ToArray())
         {
         {
-            var directoryPeers = _directorySelector.GetPeers().ToList();
+            HasDebuggerAttached = Debugger.IsAttached
+        };
+    }
+
+    private async Task TryRegisterOnDirectoryAsync(IBus bus, PeerDescriptor self)
+    {
+        var directoryPeers = _directorySelector.GetPeers().ToList();
 
 
-            foreach (var directoryPeer in directoryPeers)
+        foreach (var directoryPeer in directoryPeers)
+        {
+            try
             {
             {
-                try
+                var result = await bus.Send(new RegisterPeerCommand(self), directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
+                if (result.ErrorCode == DirectoryErrorCodes.PeerAlreadyExists)
                 {
                 {
-                    var result = await bus.Send(new RegisterPeerCommand(self), directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
-                    if (result.ErrorCode == DirectoryErrorCodes.PeerAlreadyExists)
-                    {
-                        _logger.LogWarning($"Register rejected for {self.PeerId}, the peer already exists in the directory");
-                        throw new InvalidOperationException($"Unable to register peer on directory, {self.PeerId} already exists");
-                    }
-
-                    if (result.IsSuccess && result.Response is RegisterPeerResponse response)
-                    {
-                        response.PeerDescriptors?.ForEach(peer => AddOrUpdatePeerEntry(peer, shouldRaisePeerUpdated: false));
-                        return;
-                    }
-
-                    _logger.LogWarning($"Register failed on [{directoryPeer.EndPoint}], trying next directory peer");
+                    _logger.LogWarning($"Register rejected for {self.PeerId}, the peer already exists in the directory");
+                    throw new InvalidOperationException($"Unable to register peer on directory, {self.PeerId} already exists");
                 }
                 }
-                catch (TimeoutException)
+
+                if (result.IsSuccess && result.Response is RegisterPeerResponse response)
                 {
                 {
-                    _logger.LogWarning($"Register timeout on [{directoryPeer.EndPoint}], trying next directory peer");
+                    response.PeerDescriptors?.ForEach(peer => AddOrUpdatePeerEntry(peer, shouldRaisePeerUpdated: false));
+                    return;
                 }
                 }
 
 
-                _directorySelector.SetFaultedDirectory(directoryPeer);
+                _logger.LogWarning($"Register failed on [{directoryPeer.EndPoint}], trying next directory peer");
+            }
+            catch (TimeoutException)
+            {
+                _logger.LogWarning($"Register timeout on [{directoryPeer.EndPoint}], trying next directory peer");
             }
             }
 
 
-            var directoryPeersText = string.Join(", ", directoryPeers.Select(peer => $"[{peer.EndPoint}]"));
-            var message = $"Unable to register peer on directory (tried: {directoryPeersText}) after {_configuration.RegistrationTimeout}";
-            throw new TimeoutException(message);
+            _directorySelector.SetFaultedDirectory(directoryPeer);
         }
         }
 
 
-        public async Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes)
-        {
-            var subscriptions = subscriptionsForTypes as SubscriptionsForType[] ?? subscriptionsForTypes.ToArray();
-            if (subscriptions.Length == 0)
-                return;
+        var directoryPeersText = string.Join(", ", directoryPeers.Select(peer => $"[{peer.EndPoint}]"));
+        var message = $"Unable to register peer on directory (tried: {directoryPeersText}) after {_configuration.RegistrationTimeout}";
+        throw new TimeoutException(message);
+    }
 
 
-            var command = new UpdatePeerSubscriptionsForTypesCommand(_self.Id, _timestampProvider.NextUtcTimestamp(), subscriptions);
+    public async Task UpdateSubscriptionsAsync(IBus bus, IEnumerable<SubscriptionsForType> subscriptionsForTypes)
+    {
+        var subscriptions = subscriptionsForTypes as SubscriptionsForType[] ?? subscriptionsForTypes.ToArray();
+        if (subscriptions.Length == 0)
+            return;
 
 
-            foreach (var directoryPeer in _directorySelector.GetPeers())
-            {
-                try
-                {
-                    var response = await bus.Send(command, directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
-                    if (response.IsSuccess)
-                        return;
+        var command = new UpdatePeerSubscriptionsForTypesCommand(_self.Id, _timestampProvider.NextUtcTimestamp(), subscriptions);
 
 
-                    _logger.LogWarning($"Subscription update failed on [{directoryPeer.EndPoint}], trying next directory peer");
-                }
-                catch (TimeoutException)
-                {
-                    _logger.LogWarning($"Subscription update timeout on [{directoryPeer.EndPoint}], trying next directory peer");
-                }
+        foreach (var directoryPeer in _directorySelector.GetPeers())
+        {
+            try
+            {
+                var response = await bus.Send(command, directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
+                if (response.IsSuccess)
+                    return;
 
 
-                _directorySelector.SetFaultedDirectory(directoryPeer);
+                _logger.LogWarning($"Subscription update failed on [{directoryPeer.EndPoint}], trying next directory peer");
+            }
+            catch (TimeoutException)
+            {
+                _logger.LogWarning($"Subscription update timeout on [{directoryPeer.EndPoint}], trying next directory peer");
             }
             }
 
 
-            throw new TimeoutException("Unable to update peer subscriptions on directory");
+            _directorySelector.SetFaultedDirectory(directoryPeer);
         }
         }
 
 
-        public async Task UnregisterAsync(IBus bus)
-        {
-            var command = new UnregisterPeerCommand(_self, _timestampProvider.NextUtcTimestamp());
+        throw new TimeoutException("Unable to update peer subscriptions on directory");
+    }
+
+    public async Task UnregisterAsync(IBus bus)
+    {
+        var command = new UnregisterPeerCommand(_self, _timestampProvider.NextUtcTimestamp());
 
 
-            // Using a cache of the directory peers in case of the underlying configuration proxy values changed before stopping
-            foreach (var directoryPeer in _directorySelector.GetPeersFromCache())
+        // Using a cache of the directory peers in case of the underlying configuration proxy values changed before stopping
+        foreach (var directoryPeer in _directorySelector.GetPeersFromCache())
+        {
+            try
             {
             {
-                try
+                var response = await bus.Send(command, directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
+                if (response.IsSuccess)
                 {
                 {
-                    var response = await bus.Send(command, directoryPeer).WithTimeoutAsync(_configuration.RegistrationTimeout).ConfigureAwait(false);
-                    if (response.IsSuccess)
-                    {
-                        _pingStopwatch.Stop();
-                        return;
-                    }
-
-                    _logger.LogWarning($"Unregister failed on [{directoryPeer.EndPoint}], trying next directory peer");
-                }
-                catch (TimeoutException)
-                {
-                    _logger.LogWarning($"Unregister timeout on [{directoryPeer.EndPoint}], trying next directory peer");
+                    _pingStopwatch.Stop();
+                    return;
                 }
                 }
 
 
-                _directorySelector.SetFaultedDirectory(directoryPeer);
+                _logger.LogWarning($"Unregister failed on [{directoryPeer.EndPoint}], trying next directory peer");
+            }
+            catch (TimeoutException)
+            {
+                _logger.LogWarning($"Unregister timeout on [{directoryPeer.EndPoint}], trying next directory peer");
             }
             }
 
 
-            throw new TimeoutException("Unable to unregister peer on directory");
+            _directorySelector.SetFaultedDirectory(directoryPeer);
         }
         }
 
 
-        public IList<Peer> GetPeersHandlingMessage(IMessage message)
-            => GetPeersHandlingMessage(MessageBinding.FromMessage(message));
+        throw new TimeoutException("Unable to unregister peer on directory");
+    }
 
 
-        public IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding)
-        {
-            var subscriptionList = _globalSubscriptionsIndex.GetValueOrDefault(messageBinding.MessageTypeId);
-            if (subscriptionList == null)
-                return Array.Empty<Peer>();
+    public IList<Peer> GetPeersHandlingMessage(IMessage message)
+        => GetPeersHandlingMessage(MessageBinding.FromMessage(message));
 
 
-            return subscriptionList.GetPeers(messageBinding.RoutingContent);
-        }
+    public IList<Peer> GetPeersHandlingMessage(MessageBinding messageBinding)
+    {
+        var subscriptionList = _globalSubscriptionsIndex.GetValueOrDefault(messageBinding.MessageTypeId);
+        if (subscriptionList == null)
+            return Array.Empty<Peer>();
 
 
-        public bool IsPersistent(PeerId peerId)
-        {
-            var entry = _peers.GetValueOrDefault(peerId);
-            return entry != null && entry.IsPersistent;
-        }
+        return subscriptionList.GetPeers(messageBinding.RoutingContent);
+    }
 
 
-        public Peer? GetPeer(PeerId peerId)
-        {
-            return _peers.TryGetValue(peerId, out var entry) ? entry.Peer : null;
-        }
+    public bool IsPersistent(PeerId peerId)
+    {
+        var entry = _peers.GetValueOrDefault(peerId);
+        return entry != null && entry.IsPersistent;
+    }
 
 
-        public void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types)
-        {
-            _observedSubscriptionMessageTypes = types.ToHashSet();
-        }
+    public Peer? GetPeer(PeerId peerId)
+    {
+        return _peers.TryGetValue(peerId, out var entry) ? entry.Peer : null;
+    }
 
 
-        public PeerDescriptor? GetPeerDescriptor(PeerId peerId)
-            => _peers.GetValueOrDefault(peerId)?.ToPeerDescriptor();
+    public void EnableSubscriptionsUpdatedFor(IEnumerable<Type> types)
+    {
+        _observedSubscriptionMessageTypes = types.ToHashSet();
+    }
 
 
-        public IEnumerable<PeerDescriptor> GetPeerDescriptors()
-            => _peers.Values.Select(x => x.ToPeerDescriptor()).ToList();
+    public PeerDescriptor? GetPeerDescriptor(PeerId peerId)
+        => _peers.GetValueOrDefault(peerId)?.ToPeerDescriptor();
 
 
-        private void AddOrUpdatePeerEntry(PeerDescriptor peerDescriptor, bool shouldRaisePeerUpdated)
-        {
-            var subscriptions = peerDescriptor.Subscriptions ?? Array.Empty<Subscription>();
+    public IEnumerable<PeerDescriptor> GetPeerDescriptors()
+        => _peers.Values.Select(x => x.ToPeerDescriptor()).ToList();
 
 
-            var peerEntry = _peers.AddOrUpdate(peerDescriptor.PeerId, key => CreatePeerEntry(), (key, entry) => UpdatePeerEntry(entry));
-            peerEntry.SetSubscriptions(subscriptions, peerDescriptor.TimestampUtc);
+    private void AddOrUpdatePeerEntry(PeerDescriptor peerDescriptor, bool shouldRaisePeerUpdated)
+    {
+        var subscriptions = peerDescriptor.Subscriptions ?? Array.Empty<Subscription>();
 
 
-            if (shouldRaisePeerUpdated)
-                PeerUpdated?.Invoke(peerDescriptor.Peer.Id, PeerUpdateAction.Started);
+        var peerEntry = _peers.AddOrUpdate(peerDescriptor.PeerId, key => CreatePeerEntry(), (key, entry) => UpdatePeerEntry(entry));
+        peerEntry.SetSubscriptions(subscriptions, peerDescriptor.TimestampUtc);
 
 
-            var observedSubscriptions = GetObservedSubscriptions(subscriptions);
-            if (observedSubscriptions.Count > 0)
-                PeerSubscriptionsUpdated?.Invoke(peerDescriptor.PeerId, observedSubscriptions);
+        if (shouldRaisePeerUpdated)
+            PeerUpdated?.Invoke(peerDescriptor.Peer.Id, PeerUpdateAction.Started);
 
 
-            PeerEntry CreatePeerEntry() => new PeerEntry(peerDescriptor, _globalSubscriptionsIndex);
+        var observedSubscriptions = GetObservedSubscriptions(subscriptions);
+        if (observedSubscriptions.Count > 0)
+            PeerSubscriptionsUpdated?.Invoke(peerDescriptor.PeerId, observedSubscriptions);
 
 
-            PeerEntry UpdatePeerEntry(PeerEntry entry)
-            {
-                entry.Peer.EndPoint = peerDescriptor.Peer.EndPoint;
-                entry.Peer.IsUp = peerDescriptor.Peer.IsUp;
-                entry.Peer.IsResponding = peerDescriptor.Peer.IsResponding;
-                entry.IsPersistent = peerDescriptor.IsPersistent;
-                entry.TimestampUtc = peerDescriptor.TimestampUtc ?? DateTime.UtcNow;
-                entry.HasDebuggerAttached = peerDescriptor.HasDebuggerAttached;
-
-                return entry;
-            }
-        }
+        PeerEntry CreatePeerEntry() => new(peerDescriptor, _globalSubscriptionsIndex);
 
 
-        public void Handle(PeerStarted message)
+        PeerEntry UpdatePeerEntry(PeerEntry entry)
         {
         {
-            if (EnqueueIfRegistering(message))
-                return;
-
-            AddOrUpdatePeerEntry(message.PeerDescriptor, true);
+            entry.Peer.EndPoint = peerDescriptor.Peer.EndPoint;
+            entry.Peer.IsUp = peerDescriptor.Peer.IsUp;
+            entry.Peer.IsResponding = peerDescriptor.Peer.IsResponding;
+            entry.IsPersistent = peerDescriptor.IsPersistent;
+            entry.TimestampUtc = peerDescriptor.TimestampUtc ?? DateTime.UtcNow;
+            entry.HasDebuggerAttached = peerDescriptor.HasDebuggerAttached;
+
+            return entry;
         }
         }
+    }
 
 
-        private bool EnqueueIfRegistering(IEvent message)
-        {
-            if (_messagesReceivedDuringRegister.IsAddingCompleted)
-                return false;
+    public void Handle(PeerStarted message)
+    {
+        if (EnqueueIfRegistering(message))
+            return;
 
 
-            try
-            {
-                _messagesReceivedDuringRegister.Add(message);
-                return true;
-            }
-            catch (InvalidOperationException)
-            {
-                // if adding is complete; should only happen in a race
-                return false;
-            }
-        }
+        AddOrUpdatePeerEntry(message.PeerDescriptor, true);
+    }
+
+    private bool EnqueueIfRegistering(IEvent message)
+    {
+        if (_messagesReceivedDuringRegister.IsAddingCompleted)
+            return false;
 
 
-        public void Handle(PingPeerCommand message)
+        try
         {
         {
-            if (_pingStopwatch.IsRunning)
-                _pingStopwatch.Restart();
+            _messagesReceivedDuringRegister.Add(message);
+            return true;
         }
         }
-
-        public void Handle(PeerStopped message)
+        catch (InvalidOperationException)
         {
         {
-            if (EnqueueIfRegistering(message))
-                return;
-
-            var peer = GetPeerCheckTimestamp(message.PeerId, message.TimestampUtc);
-            if (peer.Value == null)
-                return;
-
-            peer.Value.Peer.IsUp = false;
-            peer.Value.Peer.IsResponding = false;
-            peer.Value.TimestampUtc = message.TimestampUtc ?? DateTime.UtcNow;
-
-            PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Stopped);
+            // if adding is complete; should only happen in a race
+            return false;
         }
         }
+    }
 
 
-        public void Handle(PeerDecommissioned message)
-        {
-            if (EnqueueIfRegistering(message))
-                return;
+    public void Handle(PingPeerCommand message)
+    {
+        if (_pingStopwatch.IsRunning)
+            _pingStopwatch.Restart();
+    }
 
 
-            if (!_peers.TryRemove(message.PeerId, out var removedPeer))
-                return;
+    public void Handle(PeerStopped message)
+    {
+        if (EnqueueIfRegistering(message))
+            return;
 
 
-            removedPeer.RemoveSubscriptions();
+        var peer = GetPeerCheckTimestamp(message.PeerId, message.TimestampUtc);
+        if (peer.Value == null)
+            return;
 
 
-            PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Decommissioned);
-        }
+        peer.Value.Peer.IsUp = false;
+        peer.Value.Peer.IsResponding = false;
+        peer.Value.TimestampUtc = message.TimestampUtc ?? DateTime.UtcNow;
 
 
-        public void Handle(PeerSubscriptionsUpdated message)
-        {
-            if (EnqueueIfRegistering(message))
-                return;
+        PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Stopped);
+    }
 
 
-            var peer = GetPeerCheckTimestamp(message.PeerDescriptor.Peer.Id, message.PeerDescriptor.TimestampUtc);
-            if (peer.Value == null)
-            {
-                WarnWhenPeerDoesNotExist(peer, message.PeerDescriptor.PeerId);
-                return;
-            }
+    public void Handle(PeerDecommissioned message)
+    {
+        if (EnqueueIfRegistering(message))
+            return;
 
 
-            var subscriptions = message.PeerDescriptor.Subscriptions ?? Array.Empty<Subscription>();
+        if (!_peers.TryRemove(message.PeerId, out var removedPeer))
+            return;
 
 
-            peer.Value.SetSubscriptions(subscriptions, message.PeerDescriptor.TimestampUtc);
-            peer.Value.TimestampUtc = message.PeerDescriptor.TimestampUtc ?? DateTime.UtcNow;
+        removedPeer.RemoveSubscriptions();
 
 
-            PeerUpdated?.Invoke(message.PeerDescriptor.PeerId, PeerUpdateAction.Updated);
+        PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Decommissioned);
+    }
 
 
-            var observedSubscriptions = GetObservedSubscriptions(subscriptions);
-            if (observedSubscriptions.Count > 0)
-                PeerSubscriptionsUpdated?.Invoke(message.PeerDescriptor.PeerId, observedSubscriptions);
-        }
+    public void Handle(PeerSubscriptionsUpdated message)
+    {
+        if (EnqueueIfRegistering(message))
+            return;
 
 
-        private IReadOnlyList<Subscription> GetObservedSubscriptions(Subscription[] subscriptions)
+        var peer = GetPeerCheckTimestamp(message.PeerDescriptor.Peer.Id, message.PeerDescriptor.TimestampUtc);
+        if (peer.Value == null)
         {
         {
-            if (subscriptions.Length == 0)
-                return Array.Empty<Subscription>();
-
-            var observedSubscriptionMessageTypes = _observedSubscriptionMessageTypes;
-            if (observedSubscriptionMessageTypes.Count == 0)
-                return Array.Empty<Subscription>();
-
-            return subscriptions.Where(x =>
-                                {
-                                    var messageType = x.MessageTypeId.GetMessageType();
-                                    return messageType != null && observedSubscriptionMessageTypes.Contains(messageType);
-                                })
-                                .ToList();
+            WarnWhenPeerDoesNotExist(peer, message.PeerDescriptor.PeerId);
+            return;
         }
         }
 
 
-        public void Handle(PeerSubscriptionsForTypesUpdated message)
-        {
-            if (EnqueueIfRegistering(message))
-                return;
+        var subscriptions = message.PeerDescriptor.Subscriptions ?? Array.Empty<Subscription>();
 
 
-            var peer = GetPeerCheckTimestamp(message.PeerId, message.TimestampUtc);
-            if (peer.Value == null)
-            {
-                WarnWhenPeerDoesNotExist(peer, message.PeerId);
-                return;
-            }
+        peer.Value.SetSubscriptions(subscriptions, message.PeerDescriptor.TimestampUtc);
+        peer.Value.TimestampUtc = message.PeerDescriptor.TimestampUtc ?? DateTime.UtcNow;
 
 
-            var subscriptionsForTypes = message.SubscriptionsForType ?? Array.Empty<SubscriptionsForType>();
-            peer.Value.SetSubscriptionsForType(subscriptionsForTypes, message.TimestampUtc);
+        PeerUpdated?.Invoke(message.PeerDescriptor.PeerId, PeerUpdateAction.Updated);
 
 
-            PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Updated);
+        var observedSubscriptions = GetObservedSubscriptions(subscriptions);
+        if (observedSubscriptions.Count > 0)
+            PeerSubscriptionsUpdated?.Invoke(message.PeerDescriptor.PeerId, observedSubscriptions);
+    }
 
 
-            var observedSubscriptions = GetObservedSubscriptions(subscriptionsForTypes);
-            if (observedSubscriptions.Count > 0)
-                PeerSubscriptionsUpdated?.Invoke(message.PeerId, observedSubscriptions);
-        }
+    private IReadOnlyList<Subscription> GetObservedSubscriptions(Subscription[] subscriptions)
+    {
+        if (subscriptions.Length == 0)
+            return Array.Empty<Subscription>();
+
+        var observedSubscriptionMessageTypes = _observedSubscriptionMessageTypes;
+        if (observedSubscriptionMessageTypes.Count == 0)
+            return Array.Empty<Subscription>();
+
+        return subscriptions.Where(x =>
+                            {
+                                var messageType = x.MessageTypeId.GetMessageType();
+                                return messageType != null && observedSubscriptionMessageTypes.Contains(messageType);
+                            })
+                            .ToList();
+    }
 
 
-        private IReadOnlyList<Subscription> GetObservedSubscriptions(SubscriptionsForType[] subscriptionsForTypes)
-        {
-            if (subscriptionsForTypes.Length == 0)
-                return Array.Empty<Subscription>();
-
-            var observedSubscriptionMessageTypes = _observedSubscriptionMessageTypes;
-            if (observedSubscriptionMessageTypes.Count == 0)
-                return Array.Empty<Subscription>();
-
-            return subscriptionsForTypes.Where(x =>
-                                        {
-                                            var messageType = x.MessageTypeId.GetMessageType();
-                                            return messageType != null && observedSubscriptionMessageTypes.Contains(messageType);
-                                        })
-                                        .SelectMany(x => x.ToSubscriptions())
-                                        .ToList();
-        }
+    public void Handle(PeerSubscriptionsForTypesUpdated message)
+    {
+        if (EnqueueIfRegistering(message))
+            return;
 
 
-        private static void WarnWhenPeerDoesNotExist(PeerEntryResult peer, PeerId peerId)
+        var peer = GetPeerCheckTimestamp(message.PeerId, message.TimestampUtc);
+        if (peer.Value == null)
         {
         {
-            if (peer.FailureReason == PeerEntryResult.FailureReasonType.PeerNotPresent)
-                _logger.LogWarning($"Received message but no peer existed: {peerId}");
+            WarnWhenPeerDoesNotExist(peer, message.PeerId);
+            return;
         }
         }
 
 
-        public void Handle(PeerNotResponding message)
-        {
-            HandlePeerRespondingChange(message.PeerId, false);
-        }
+        var subscriptionsForTypes = message.SubscriptionsForType ?? Array.Empty<SubscriptionsForType>();
+        peer.Value.SetSubscriptionsForType(subscriptionsForTypes, message.TimestampUtc);
 
 
-        public void Handle(PeerResponding message)
-        {
-            HandlePeerRespondingChange(message.PeerId, true);
-        }
+        PeerUpdated?.Invoke(message.PeerId, PeerUpdateAction.Updated);
 
 
-        private void HandlePeerRespondingChange(PeerId peerId, bool isResponding)
-        {
-            var peer = _peers.GetValueOrDefault(peerId);
-            if (peer == null)
-                return;
+        var observedSubscriptions = GetObservedSubscriptions(subscriptionsForTypes);
+        if (observedSubscriptions.Count > 0)
+            PeerSubscriptionsUpdated?.Invoke(message.PeerId, observedSubscriptions);
+    }
 
 
-            peer.Peer.IsResponding = isResponding;
+    private IReadOnlyList<Subscription> GetObservedSubscriptions(SubscriptionsForType[] subscriptionsForTypes)
+    {
+        if (subscriptionsForTypes.Length == 0)
+            return Array.Empty<Subscription>();
+
+        var observedSubscriptionMessageTypes = _observedSubscriptionMessageTypes;
+        if (observedSubscriptionMessageTypes.Count == 0)
+            return Array.Empty<Subscription>();
+
+        return subscriptionsForTypes.Where(x =>
+                                    {
+                                        var messageType = x.MessageTypeId.GetMessageType();
+                                        return messageType != null && observedSubscriptionMessageTypes.Contains(messageType);
+                                    })
+                                    .SelectMany(x => x.ToSubscriptions())
+                                    .ToList();
+    }
 
 
-            PeerUpdated?.Invoke(peerId, PeerUpdateAction.Updated);
-        }
+    private static void WarnWhenPeerDoesNotExist(PeerEntryResult peer, PeerId peerId)
+    {
+        if (peer.FailureReason == PeerEntryResult.FailureReasonType.PeerNotPresent)
+            _logger.LogWarning($"Received message but no peer existed: {peerId}");
+    }
 
 
-        private PeerEntryResult GetPeerCheckTimestamp(PeerId peerId, DateTime? timestampUtc)
-        {
-            var peer = _peers.GetValueOrDefault(peerId);
-            if (peer == null)
-                return new PeerEntryResult(PeerEntryResult.FailureReasonType.PeerNotPresent);
+    public void Handle(PeerNotResponding message)
+    {
+        HandlePeerRespondingChange(message.PeerId, false);
+    }
 
 
-            if (peer.TimestampUtc > timestampUtc)
-            {
-                _logger.LogInformation("Outdated message ignored");
-                return new PeerEntryResult(PeerEntryResult.FailureReasonType.OutdatedMessage);
-            }
+    public void Handle(PeerResponding message)
+    {
+        HandlePeerRespondingChange(message.PeerId, true);
+    }
 
 
-            return new PeerEntryResult(peer);
-        }
+    private void HandlePeerRespondingChange(PeerId peerId, bool isResponding)
+    {
+        var peer = _peers.GetValueOrDefault(peerId);
+        if (peer == null)
+            return;
 
 
-        private readonly struct PeerEntryResult
+        peer.Peer.IsResponding = isResponding;
+
+        PeerUpdated?.Invoke(peerId, PeerUpdateAction.Updated);
+    }
+
+    private PeerEntryResult GetPeerCheckTimestamp(PeerId peerId, DateTime? timestampUtc)
+    {
+        var peer = _peers.GetValueOrDefault(peerId);
+        if (peer == null)
+            return new PeerEntryResult(PeerEntryResult.FailureReasonType.PeerNotPresent);
+
+        if (peer.TimestampUtc > timestampUtc)
         {
         {
-            internal enum FailureReasonType
-            {
-                PeerNotPresent,
-                OutdatedMessage,
-            }
+            _logger.LogInformation("Outdated message ignored");
+            return new PeerEntryResult(PeerEntryResult.FailureReasonType.OutdatedMessage);
+        }
 
 
-            public PeerEntryResult(PeerEntry value)
-            {
-                Value = value;
-                FailureReason = null;
-            }
+        return new PeerEntryResult(peer);
+    }
 
 
-            public PeerEntryResult(FailureReasonType failureReason)
-            {
-                Value = null;
-                FailureReason = failureReason;
-            }
+    private readonly struct PeerEntryResult
+    {
+        internal enum FailureReasonType
+        {
+            PeerNotPresent,
+            OutdatedMessage,
+        }
 
 
-            public PeerEntry? Value { get; }
-            public FailureReasonType? FailureReason { get; }
+        public PeerEntryResult(PeerEntry value)
+        {
+            Value = value;
+            FailureReason = null;
         }
         }
+
+        public PeerEntryResult(FailureReasonType failureReason)
+        {
+            Value = null;
+            FailureReason = failureReason;
+        }
+
+        public PeerEntry? Value { get; }
+        public FailureReasonType? FailureReason { get; }
     }
     }
 }
 }

+ 15 - 15
src/Abc.Zebus/Directory/PeerNotResponding.cs

@@ -2,23 +2,23 @@
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class PeerNotResponding : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = false)]
-        public readonly DateTime? TimestampUtc;
+[ProtoContract, Transient]
+public class PeerNotResponding : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public PeerNotResponding(PeerId peerId, DateTime? timestampUtc = null)
-        {
-            PeerId = peerId;
-            TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
-        }
+    [ProtoMember(2, IsRequired = false)]
+    public readonly DateTime? TimestampUtc;
 
 
-        public override string ToString() => PeerId.ToString();
+    public PeerNotResponding(PeerId peerId, DateTime? timestampUtc = null)
+    {
+        PeerId = peerId;
+        TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
     }
     }
+
+    public override string ToString()
+        => PeerId.ToString();
 }
 }

+ 15 - 15
src/Abc.Zebus/Directory/PeerResponding.cs

@@ -2,23 +2,23 @@
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class PeerResponding : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = false)]
-        public readonly DateTime? TimestampUtc;
+[ProtoContract, Transient]
+public class PeerResponding : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        public PeerResponding(PeerId peerId, DateTime? timestampUtc = null)
-        {
-            PeerId = peerId;
-            TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
-        }
+    [ProtoMember(2, IsRequired = false)]
+    public readonly DateTime? TimestampUtc;
 
 
-        public override string ToString() => PeerId.ToString();
+    public PeerResponding(PeerId peerId, DateTime? timestampUtc = null)
+    {
+        PeerId = peerId;
+        TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
     }
     }
+
+    public override string ToString()
+        => PeerId.ToString();
 }
 }

+ 13 - 13
src/Abc.Zebus/Directory/PeerStarted.cs

@@ -1,18 +1,18 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public sealed class PeerStarted : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerDescriptor PeerDescriptor;
+namespace Abc.Zebus.Directory;
 
 
-        public PeerStarted(PeerDescriptor peerDescriptor)
-        {
-            PeerDescriptor = peerDescriptor;
-        }
+[ProtoContract, Transient]
+public sealed class PeerStarted : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerDescriptor PeerDescriptor;
 
 
-        public override string ToString() => $"{PeerDescriptor.Peer} TimestampUtc: {PeerDescriptor.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public PeerStarted(PeerDescriptor peerDescriptor)
+    {
+        PeerDescriptor = peerDescriptor;
     }
     }
-}
+
+    public override string ToString()
+        => $"{PeerDescriptor.Peer} TimestampUtc: {PeerDescriptor.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+}

+ 22 - 22
src/Abc.Zebus/Directory/PeerStopped.cs

@@ -2,32 +2,32 @@
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class PeerStopped : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = false)]
-        public readonly string? PeerEndPoint;
+[ProtoContract, Transient]
+public class PeerStopped : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        [ProtoMember(3, IsRequired = false)]
-        public readonly DateTime? TimestampUtc;
+    [ProtoMember(2, IsRequired = false)]
+    public readonly string? PeerEndPoint;
 
 
-        public PeerStopped(Peer peer, DateTime? timestampUtc = null)
-            : this(peer.Id, peer.EndPoint, timestampUtc)
-        {
-        }
+    [ProtoMember(3, IsRequired = false)]
+    public readonly DateTime? TimestampUtc;
 
 
-        public PeerStopped(PeerId peerId, string? peerEndPoint, DateTime? timestampUtc = null)
-        {
-            PeerId = peerId;
-            PeerEndPoint = peerEndPoint;
-            TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
-        }
+    public PeerStopped(Peer peer, DateTime? timestampUtc = null)
+        : this(peer.Id, peer.EndPoint, timestampUtc)
+    {
+    }
 
 
-        public override string ToString() => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public PeerStopped(PeerId peerId, string? peerEndPoint, DateTime? timestampUtc = null)
+    {
+        PeerId = peerId;
+        PeerEndPoint = peerEndPoint;
+        TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
     }
     }
+
+    public override string ToString()
+        => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
 }
 }

+ 196 - 197
src/Abc.Zebus/Directory/PeerSubscriptionTree.cs

@@ -6,279 +6,278 @@ using System.Linq;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Directory
-{
-    public class PeerSubscriptionTree
-    {
-        private readonly SubscriptionNode _rootNode = new SubscriptionNode(0);
-        private List<Peer> _peersMatchingAllMessages = new List<Peer>();
+namespace Abc.Zebus.Directory;
 
 
-        public bool IsEmpty => _rootNode.IsEmpty && _peersMatchingAllMessages.Count == 0;
+public class PeerSubscriptionTree
+{
+    private readonly SubscriptionNode _rootNode = new(0);
+    private List<Peer> _peersMatchingAllMessages = new();
 
 
-        public void Add(Peer peer, BindingKey subscription)
-            => UpdatePeerSubscription(peer, subscription, UpdateAction.Add);
+    public bool IsEmpty => _rootNode.IsEmpty && _peersMatchingAllMessages.Count == 0;
 
 
-        public void Remove(Peer peer, BindingKey subscription)
-            => UpdatePeerSubscription(peer, subscription, UpdateAction.Remove);
+    public void Add(Peer peer, BindingKey subscription)
+        => UpdatePeerSubscription(peer, subscription, UpdateAction.Add);
 
 
-        public IList<Peer> GetPeers(RoutingContent routingContent)
-        {
-            var peerCollector = new PeerCollector(_peersMatchingAllMessages);
+    public void Remove(Peer peer, BindingKey subscription)
+        => UpdatePeerSubscription(peer, subscription, UpdateAction.Remove);
 
 
-            if (routingContent.IsEmpty)
-            {
-                // The message is not routable or has no routing member.
+    public IList<Peer> GetPeers(RoutingContent routingContent)
+    {
+        var peerCollector = new PeerCollector(_peersMatchingAllMessages);
 
 
-                // If the tree contains any subscription with a binding key, it indicates a message definition
-                // mismatch between the publisher and the subscriber. In this situation, it is safer to send
-                // the message to the subscriber anyway.
+        if (routingContent.IsEmpty)
+        {
+            // The message is not routable or has no routing member.
 
 
-                // => Always forward the message to all peers.
+            // If the tree contains any subscription with a binding key, it indicates a message definition
+            // mismatch between the publisher and the subscriber. In this situation, it is safer to send
+            // the message to the subscriber anyway.
 
 
-                _rootNode.AddAllPeers(peerCollector);
-            }
-            else
-            {
-                _rootNode.Accept(peerCollector, routingContent);
-            }
+            // => Always forward the message to all peers.
 
 
-            return peerCollector.GetPeers();
+            _rootNode.AddAllPeers(peerCollector);
         }
         }
-
-        private void UpdatePeerSubscription(Peer peer, BindingKey subscription, UpdateAction action)
+        else
         {
         {
-            if (subscription.IsEmpty)
-                UpdatePeersMatchingAllMessages(peer, action);
-            else
-                _rootNode.Update(peer, subscription, action);
+            _rootNode.Accept(peerCollector, routingContent);
         }
         }
 
 
-        private void UpdatePeersMatchingAllMessages(Peer peer, UpdateAction action)
-            => UpdateList(ref _peersMatchingAllMessages, peer, action);
+        return peerCollector.GetPeers();
+    }
 
 
-        private static void UpdateList(ref List<Peer> peers, Peer peer, UpdateAction action)
-        {
-            var newPeers = new List<Peer>(peers.Capacity);
-            newPeers.AddRange(peers.Where(x => x.Id != peer.Id));
+    private void UpdatePeerSubscription(Peer peer, BindingKey subscription, UpdateAction action)
+    {
+        if (subscription.IsEmpty)
+            UpdatePeersMatchingAllMessages(peer, action);
+        else
+            _rootNode.Update(peer, subscription, action);
+    }
 
 
-            if (action == UpdateAction.Add)
-                newPeers.Add(peer);
+    private void UpdatePeersMatchingAllMessages(Peer peer, UpdateAction action)
+        => UpdateList(ref _peersMatchingAllMessages, peer, action);
 
 
-            peers = newPeers;
-        }
+    private static void UpdateList(ref List<Peer> peers, Peer peer, UpdateAction action)
+    {
+        var newPeers = new List<Peer>(peers.Capacity);
+        newPeers.AddRange(peers.Where(x => x.Id != peer.Id));
+
+        if (action == UpdateAction.Add)
+            newPeers.Add(peer);
 
 
-        private class PeerCollector
+        peers = newPeers;
+    }
+
+    private class PeerCollector
+    {
+        private readonly Dictionary<PeerId, Peer> _collectedPeers = new();
+        private readonly List<Peer> _initialPeers;
+
+        public PeerCollector(List<Peer> initialPeers)
         {
         {
-            private readonly Dictionary<PeerId, Peer> _collectedPeers = new Dictionary<PeerId, Peer>();
-            private readonly List<Peer> _initialPeers;
+            _initialPeers = initialPeers;
+        }
 
 
-            public PeerCollector(List<Peer> initialPeers)
+        [SuppressMessage("ReSharper", "ParameterTypeCanBeEnumerable.Local")]
+        public void Offer(List<Peer> peers)
+        {
+            foreach (var peer in peers)
             {
             {
-                _initialPeers = initialPeers;
+                _collectedPeers[peer.Id] = peer;
             }
             }
+        }
 
 
-            [SuppressMessage("ReSharper", "ParameterTypeCanBeEnumerable.Local")]
-            public void Offer(List<Peer> peers)
-            {
-                foreach (var peer in peers)
-                {
-                    _collectedPeers[peer.Id] = peer;
-                }
-            }
+        public List<Peer> GetPeers()
+        {
+            if (_collectedPeers.Count == 0)
+                return _initialPeers;
 
 
-            public List<Peer> GetPeers()
-            {
-                if (_collectedPeers.Count == 0)
-                    return _initialPeers;
+            Offer(_initialPeers);
 
 
-                Offer(_initialPeers);
+            return _collectedPeers.Values.ToList();
+        }
+    }
 
 
-                return _collectedPeers.Values.ToList();
-            }
+    private class SubscriptionNode
+    {
+        private static readonly Action<SubscriptionNode, string> _removeNode = (x, part) => x.RemoveChildNode(part);
+        private static readonly Action<SubscriptionNode, string?> _removeSharpNode = (x, _) => x._sharpNode = null;
+        private static readonly Action<SubscriptionNode, string?> _removeStarNode = (x, _) => x._starNode = null;
+
+        private ConcurrentDictionary<string, SubscriptionNode>? _childNodes;
+        private SubscriptionNode? _sharpNode;
+        private SubscriptionNode? _starNode;
+
+        private readonly int _nextPartIndex;
+        private List<Peer> _peers = new();
+        private int _peerCountIncludingChildren;
+
+        public SubscriptionNode(int nextPartIndex)
+        {
+            _nextPartIndex = nextPartIndex;
         }
         }
 
 
-        private class SubscriptionNode
+        public bool IsEmpty => _peerCountIncludingChildren == 0;
+
+        public void AddAllPeers(PeerCollector peerCollector)
         {
         {
-            private static readonly Action<SubscriptionNode, string> _removeNode = (x, part) => x.RemoveChildNode(part);
-            private static readonly Action<SubscriptionNode, string?> _removeSharpNode = (x, _) => x._sharpNode = null;
-            private static readonly Action<SubscriptionNode, string?> _removeStarNode = (x, _) => x._starNode = null;
+            peerCollector.Offer(_peers);
 
 
-            private ConcurrentDictionary<string, SubscriptionNode>? _childNodes;
-            private SubscriptionNode? _sharpNode;
-            private SubscriptionNode? _starNode;
+            _sharpNode?.AddAllPeers(peerCollector);
+            _starNode?.AddAllPeers(peerCollector);
 
 
-            private readonly int _nextPartIndex;
-            private List<Peer> _peers = new List<Peer>();
-            private int _peerCountIncludingChildren;
+            if (_childNodes == null)
+                return;
 
 
-            public SubscriptionNode(int nextPartIndex)
+            foreach (var (_, childNode) in _childNodes)
             {
             {
-                _nextPartIndex = nextPartIndex;
+                childNode.AddAllPeers(peerCollector);
             }
             }
+        }
 
 
-            public bool IsEmpty => _peerCountIncludingChildren == 0;
-
-            public void AddAllPeers(PeerCollector peerCollector)
+        public void Accept(PeerCollector peerCollector, RoutingContent routingContent)
+        {
+            if (IsLeaf(routingContent))
             {
             {
                 peerCollector.Offer(_peers);
                 peerCollector.Offer(_peers);
-
-                _sharpNode?.AddAllPeers(peerCollector);
-                _starNode?.AddAllPeers(peerCollector);
-
-                if (_childNodes == null)
-                    return;
-
-                foreach (var (_, childNode) in _childNodes)
-                {
-                    childNode.AddAllPeers(peerCollector);
-                }
+                return;
             }
             }
 
 
-            public void Accept(PeerCollector peerCollector, RoutingContent routingContent)
-            {
-                if (IsLeaf(routingContent))
-                {
-                    peerCollector.Offer(_peers);
-                    return;
-                }
+            _sharpNode?.AddAllPeers(peerCollector);
+            _starNode?.Accept(peerCollector, routingContent);
 
 
-                _sharpNode?.AddAllPeers(peerCollector);
-                _starNode?.Accept(peerCollector, routingContent);
+            if (_childNodes == null)
+                return;
 
 
-                if (_childNodes == null)
-                    return;
+            var partValue = routingContent[_nextPartIndex];
 
 
-                var partValue = routingContent[_nextPartIndex];
+            if (partValue.IsSingle)
+            {
+                // Almost all RoutingContentValue are expected to contain a single value.
 
 
-                if (partValue.IsSingle)
+                if (partValue.SingleValue != null && _childNodes.TryGetValue(partValue.SingleValue, out var childNode))
+                    childNode.Accept(peerCollector, routingContent);
+            }
+            else
+            {
+                foreach (var token in partValue.GetValues())
                 {
                 {
-                    // Almost all RoutingContentValue are expected to contain a single value.
-
-                    if (partValue.SingleValue != null && _childNodes.TryGetValue(partValue.SingleValue, out var childNode))
+                    if (token != null && _childNodes.TryGetValue(token, out var childNode))
                         childNode.Accept(peerCollector, routingContent);
                         childNode.Accept(peerCollector, routingContent);
                 }
                 }
-                else
-                {
-                    foreach (var token in partValue.GetValues())
-                    {
-                        if (token != null && _childNodes.TryGetValue(token, out var childNode))
-                            childNode.Accept(peerCollector, routingContent);
-                    }
-                }
             }
             }
+        }
 
 
-            public int Update(Peer peer, BindingKey subscription, UpdateAction action)
+        public int Update(Peer peer, BindingKey subscription, UpdateAction action)
+        {
+            if (IsLeaf(subscription))
             {
             {
-                if (IsLeaf(subscription))
-                {
-                    var update = UpdateList(peer, action);
-                    _peerCountIncludingChildren += update;
+                var update = UpdateList(peer, action);
+                _peerCountIncludingChildren += update;
 
 
-                    return update;
-                }
+                return update;
+            }
 
 
-                var nextPart = subscription.GetPartToken(_nextPartIndex);
+            var nextPart = subscription.GetPartToken(_nextPartIndex);
 
 
-                if (subscription.IsSharp(_nextPartIndex) || nextPart == null)
-                    return UpdateChildNode(GetOrCreateSharpNode(), peer, subscription, action, null, _removeSharpNode);
+            if (subscription.IsSharp(_nextPartIndex) || nextPart == null)
+                return UpdateChildNode(GetOrCreateSharpNode(), peer, subscription, action, null, _removeSharpNode);
 
 
-                if (subscription.IsStar(_nextPartIndex))
-                    return UpdateChildNode(GetOrCreateStarNode(), peer, subscription, action, null, _removeStarNode);
+            if (subscription.IsStar(_nextPartIndex))
+                return UpdateChildNode(GetOrCreateStarNode(), peer, subscription, action, null, _removeStarNode);
 
 
-                var childNode = GetOrAddChildNode(nextPart);
-                return UpdateChildNode(childNode, peer, subscription, action, nextPart, _removeNode!);
-            }
+            var childNode = GetOrAddChildNode(nextPart);
+            return UpdateChildNode(childNode, peer, subscription, action, nextPart, _removeNode!);
+        }
 
 
-            private int UpdateChildNode(SubscriptionNode childNode, Peer peer, BindingKey subscription, UpdateAction action, string? childNodePart, Action<SubscriptionNode, string?> remover)
-            {
-                var update = childNode.Update(peer, subscription, action);
-                _peerCountIncludingChildren += update;
+        private int UpdateChildNode(SubscriptionNode childNode, Peer peer, BindingKey subscription, UpdateAction action, string? childNodePart, Action<SubscriptionNode, string?> remover)
+        {
+            var update = childNode.Update(peer, subscription, action);
+            _peerCountIncludingChildren += update;
 
 
-                if (childNode.IsEmpty)
-                    remover(this, childNodePart);
+            if (childNode.IsEmpty)
+                remover(this, childNodePart);
 
 
-                return update;
-            }
+            return update;
+        }
 
 
-            private bool IsLeaf(BindingKey bindingKey)
-            {
-                if (_nextPartIndex == 0)
-                    return false;
+        private bool IsLeaf(BindingKey bindingKey)
+        {
+            if (_nextPartIndex == 0)
+                return false;
 
 
-                return _nextPartIndex == bindingKey.PartCount;
-            }
+            return _nextPartIndex == bindingKey.PartCount;
+        }
 
 
-            private bool IsLeaf(RoutingContent routingContent)
-            {
-                if (_nextPartIndex == 0)
-                    return false;
+        private bool IsLeaf(RoutingContent routingContent)
+        {
+            if (_nextPartIndex == 0)
+                return false;
 
 
-                return _nextPartIndex == routingContent.PartCount;
-            }
+            return _nextPartIndex == routingContent.PartCount;
+        }
 
 
-            private SubscriptionNode GetOrAddChildNode(string part)
-            {
-                if (_childNodes == null)
-                    _childNodes = new ConcurrentDictionary<string, SubscriptionNode>();
+        private SubscriptionNode GetOrAddChildNode(string part)
+        {
+            if (_childNodes == null)
+                _childNodes = new ConcurrentDictionary<string, SubscriptionNode>();
 
 
-                return _childNodes.GetOrAdd(part, k => new SubscriptionNode(_nextPartIndex + 1));
-            }
+            return _childNodes.GetOrAdd(part, k => new SubscriptionNode(_nextPartIndex + 1));
+        }
 
 
-            private void RemoveChildNode(string part)
-                => _childNodes?.TryRemove(part, out _);
+        private void RemoveChildNode(string part)
+            => _childNodes?.TryRemove(part, out _);
 
 
-            private SubscriptionNode GetOrCreateSharpNode()
-                => _sharpNode ??= new SubscriptionNode(_nextPartIndex + 1);
+        private SubscriptionNode GetOrCreateSharpNode()
+            => _sharpNode ??= new SubscriptionNode(_nextPartIndex + 1);
 
 
-            private SubscriptionNode GetOrCreateStarNode()
-                => _starNode ??= new SubscriptionNode(_nextPartIndex + 1);
+        private SubscriptionNode GetOrCreateStarNode()
+            => _starNode ??= new SubscriptionNode(_nextPartIndex + 1);
 
 
-            private int UpdateList(Peer peer, UpdateAction action)
-                => action == UpdateAction.Add
-                    ? AddToList(peer)
-                    : RemoveFromList(peer);
+        private int UpdateList(Peer peer, UpdateAction action)
+            => action == UpdateAction.Add
+                ? AddToList(peer)
+                : RemoveFromList(peer);
 
 
-            private int AddToList(Peer peerToAdd)
+        private int AddToList(Peer peerToAdd)
+        {
+            var removed = false;
+            var newPeers = new List<Peer>(_peers.Capacity);
+            foreach (var peer in _peers)
             {
             {
-                var removed = false;
-                var newPeers = new List<Peer>(_peers.Capacity);
-                foreach (var peer in _peers)
-                {
-                    if (peer.Id == peerToAdd.Id)
-                        removed = true;
-                    else
-                        newPeers.Add(peer);
-                }
+                if (peer.Id == peerToAdd.Id)
+                    removed = true;
+                else
+                    newPeers.Add(peer);
+            }
 
 
-                newPeers.Add(peerToAdd);
+            newPeers.Add(peerToAdd);
 
 
-                _peers = newPeers;
+            _peers = newPeers;
 
 
-                return removed ? 0 : 1;
-            }
+            return removed ? 0 : 1;
+        }
 
 
-            private int RemoveFromList(Peer peerToRemove)
+        private int RemoveFromList(Peer peerToRemove)
+        {
+            var removed = false;
+            var newPeers = new List<Peer>(_peers.Capacity);
+            foreach (var peer in _peers)
             {
             {
-                var removed = false;
-                var newPeers = new List<Peer>(_peers.Capacity);
-                foreach (var peer in _peers)
-                {
-                    if (peer.Id == peerToRemove.Id)
-                        removed = true;
-                    else
-                        newPeers.Add(peer);
-                }
+                if (peer.Id == peerToRemove.Id)
+                    removed = true;
+                else
+                    newPeers.Add(peer);
+            }
 
 
-                _peers = newPeers;
+            _peers = newPeers;
 
 
-                return removed ? -1 : 0;
-            }
+            return removed ? -1 : 0;
         }
         }
+    }
 
 
-        private enum UpdateAction
-        {
-            Add,
-            Remove,
-        }
+    private enum UpdateAction
+    {
+        Add,
+        Remove,
     }
     }
 }
 }

+ 25 - 25
src/Abc.Zebus/Directory/PeerSubscriptionsForTypesUpdated.cs

@@ -2,34 +2,34 @@
 using ProtoBuf;
 using ProtoBuf;
 using System;
 using System;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public sealed class PeerSubscriptionsForTypesUpdated : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly SubscriptionsForType[] SubscriptionsForType;
+[ProtoContract, Transient]
+public sealed class PeerSubscriptionsForTypesUpdated : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        [ProtoMember(3, IsRequired = false)]
-        public readonly DateTime TimestampUtc;
+    [ProtoMember(2, IsRequired = true)]
+    public readonly SubscriptionsForType[] SubscriptionsForType;
 
 
-        public PeerSubscriptionsForTypesUpdated(PeerId peerId, DateTime timestampUtc, MessageTypeId messageTypeId, params BindingKey[] bindingKeys)
-        {
-            PeerId = peerId;
-            SubscriptionsForType = new [] { new SubscriptionsForType(messageTypeId, bindingKeys) };
-            TimestampUtc = timestampUtc;
-        }
+    [ProtoMember(3, IsRequired = false)]
+    public readonly DateTime TimestampUtc;
 
 
-        public PeerSubscriptionsForTypesUpdated(PeerId peerId, DateTime timestampUtc, params SubscriptionsForType[] subscriptionsForType)
-        {
-            PeerId = peerId;
-            SubscriptionsForType = subscriptionsForType;
-            TimestampUtc = timestampUtc;
-        }
+    public PeerSubscriptionsForTypesUpdated(PeerId peerId, DateTime timestampUtc, MessageTypeId messageTypeId, params BindingKey[] bindingKeys)
+    {
+        PeerId = peerId;
+        SubscriptionsForType = new [] { new SubscriptionsForType(messageTypeId, bindingKeys) };
+        TimestampUtc = timestampUtc;
+    }
 
 
-        public override string ToString() => $"PeerId: {PeerId}, TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public PeerSubscriptionsForTypesUpdated(PeerId peerId, DateTime timestampUtc, params SubscriptionsForType[] subscriptionsForType)
+    {
+        PeerId = peerId;
+        SubscriptionsForType = subscriptionsForType;
+        TimestampUtc = timestampUtc;
     }
     }
-}
+
+    public override string ToString()
+        => $"PeerId: {PeerId}, TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+}

+ 13 - 13
src/Abc.Zebus/Directory/PeerSubscriptionsUpdated.cs

@@ -1,18 +1,18 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public sealed class PeerSubscriptionsUpdated : IEvent
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerDescriptor PeerDescriptor;
+namespace Abc.Zebus.Directory;
 
 
-        public PeerSubscriptionsUpdated(PeerDescriptor peerDescriptor)
-        {
-            PeerDescriptor = peerDescriptor;
-        }
+[ProtoContract, Transient]
+public sealed class PeerSubscriptionsUpdated : IEvent
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerDescriptor PeerDescriptor;
 
 
-        public override string ToString() => $"PeerId: {PeerDescriptor.PeerId}, TimestampUtc: {PeerDescriptor.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public PeerSubscriptionsUpdated(PeerDescriptor peerDescriptor)
+    {
+        PeerDescriptor = peerDescriptor;
     }
     }
-}
+
+    public override string ToString()
+        => $"PeerId: {PeerDescriptor.PeerId}, TimestampUtc: {PeerDescriptor.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+}

+ 8 - 9
src/Abc.Zebus/Directory/PeerUpdateAction.cs

@@ -1,11 +1,10 @@
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+public enum PeerUpdateAction
 {
 {
-    public enum PeerUpdateAction
-    {
-        Stopped,
-        Started,
-        /// <summary>  Peer subscriptions are updated </summary>
-        Updated,
-        Decommissioned,
-    }
+    Stopped,
+    Started,
+    /// <summary>  Peer subscriptions are updated </summary>
+    Updated,
+    Decommissioned,
 }
 }

+ 5 - 6
src/Abc.Zebus/Directory/PingPeerCommand.cs

@@ -1,9 +1,8 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+[ProtoContract, Transient, Infrastructure]
+public sealed class PingPeerCommand : ICommand
 {
 {
-    [ProtoContract, Transient, Infrastructure]
-    public sealed class PingPeerCommand : ICommand 
-    {
-    }
-}
+}

+ 13 - 13
src/Abc.Zebus/Directory/RegisterPeerCommand.cs

@@ -1,18 +1,18 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public sealed class RegisterPeerCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerDescriptor Peer;
+namespace Abc.Zebus.Directory;
 
 
-        public RegisterPeerCommand(PeerDescriptor peer)
-        {
-            Peer = peer;
-        }
+[ProtoContract, Transient]
+public sealed class RegisterPeerCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerDescriptor Peer;
 
 
-        public override string ToString() => $"{Peer.Peer} TimestampUtc: {Peer.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public RegisterPeerCommand(PeerDescriptor peer)
+    {
+        Peer = peer;
     }
     }
-}
+
+    public override string ToString()
+        => $"{Peer.Peer} TimestampUtc: {Peer.TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+}

+ 10 - 11
src/Abc.Zebus/Directory/RegisterPeerResponse.cs

@@ -1,16 +1,15 @@
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+[ProtoContract]
+public class RegisterPeerResponse : IMessage
 {
 {
-    [ProtoContract]
-    public class RegisterPeerResponse : IMessage
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerDescriptor[] PeerDescriptors;
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerDescriptor[] PeerDescriptors;
 
 
-        public RegisterPeerResponse(PeerDescriptor[] peerDescriptors)
-        {
-            PeerDescriptors = peerDescriptors;
-        }
+    public RegisterPeerResponse(PeerDescriptor[] peerDescriptors)
+    {
+        PeerDescriptors = peerDescriptors;
     }
     }
-}
+}

+ 46 - 47
src/Abc.Zebus/Directory/SubscriptionsForType.cs

@@ -5,66 +5,65 @@ using Abc.Zebus.Routing;
 using JetBrains.Annotations;
 using JetBrains.Annotations;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
+namespace Abc.Zebus.Directory;
+
+[ProtoContract]
+public class SubscriptionsForType : IEquatable<SubscriptionsForType>
 {
 {
-    [ProtoContract]
-    public class SubscriptionsForType : IEquatable<SubscriptionsForType>
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly MessageTypeId MessageTypeId;
+    [ProtoMember(1, IsRequired = true)]
+    public readonly MessageTypeId MessageTypeId;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly BindingKey[] BindingKeys;
+    [ProtoMember(2, IsRequired = true)]
+    public readonly BindingKey[] BindingKeys;
 
 
-        public SubscriptionsForType(MessageTypeId messageTypeId, params BindingKey[] bindingKeys)
-        {
-            MessageTypeId = messageTypeId;
-            BindingKeys = bindingKeys;
-        }
+    public SubscriptionsForType(MessageTypeId messageTypeId, params BindingKey[] bindingKeys)
+    {
+        MessageTypeId = messageTypeId;
+        BindingKeys = bindingKeys;
+    }
 
 
-        [UsedImplicitly]
-        private SubscriptionsForType()
-        {
-            BindingKeys = Array.Empty<BindingKey>();
-        }
+    [UsedImplicitly]
+    private SubscriptionsForType()
+    {
+        BindingKeys = Array.Empty<BindingKey>();
+    }
 
 
-        // ReSharper disable once ConditionIsAlwaysTrueOrFalse
-        public bool HasSubscriptions => BindingKeys != null && BindingKeys.Length != 0;
+    // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+    public bool HasSubscriptions => BindingKeys != null && BindingKeys.Length != 0;
 
 
-        public static SubscriptionsForType Create<TMessage>(params BindingKey[] bindingKeys)
-            where TMessage : IMessage
-            => new SubscriptionsForType(MessageUtil.TypeId<TMessage>(), bindingKeys);
+    public static SubscriptionsForType Create<TMessage>(params BindingKey[] bindingKeys)
+        where TMessage : IMessage
+        => new SubscriptionsForType(MessageUtil.TypeId<TMessage>(), bindingKeys);
 
 
-        public static Dictionary<MessageTypeId, SubscriptionsForType> CreateDictionary(IEnumerable<Subscription> subscriptions)
-            => subscriptions.GroupBy(sub => sub.MessageTypeId)
-                            .ToDictionary(grp => grp.Key, grp => new SubscriptionsForType(grp.Key, grp.Select(sub => sub.BindingKey).ToArray()));
+    public static Dictionary<MessageTypeId, SubscriptionsForType> CreateDictionary(IEnumerable<Subscription> subscriptions)
+        => subscriptions.GroupBy(sub => sub.MessageTypeId)
+                        .ToDictionary(grp => grp.Key, grp => new SubscriptionsForType(grp.Key, grp.Select(sub => sub.BindingKey).ToArray()));
 
 
-        public Subscription[] ToSubscriptions()
-            => BindingKeys?.Select(bindingKey => new Subscription(MessageTypeId, bindingKey)).ToArray() ?? Array.Empty<Subscription>();
+    public Subscription[] ToSubscriptions()
+        => BindingKeys?.Select(bindingKey => new Subscription(MessageTypeId, bindingKey)).ToArray() ?? Array.Empty<Subscription>();
 
 
-        public IReadOnlyList<BindingKeyPart> GetBindingKeyPartsForMember(string memberName)
-            => BindingKeyUtil.GetPartsForMember(MessageTypeId, memberName, BindingKeys).ToList();
+    public IReadOnlyList<BindingKeyPart> GetBindingKeyPartsForMember(string memberName)
+        => BindingKeyUtil.GetPartsForMember(MessageTypeId, memberName, BindingKeys).ToList();
 
 
-        public bool Equals(SubscriptionsForType? other)
-            => other != null && MessageTypeId == other.MessageTypeId && BindingKeys.SequenceEqual(other.BindingKeys);
+    public bool Equals(SubscriptionsForType? other)
+        => other != null && MessageTypeId == other.MessageTypeId && BindingKeys.SequenceEqual(other.BindingKeys);
 
 
-        public override bool Equals(object? obj)
-        {
-            if (ReferenceEquals(null, obj))
-                return false;
-            if (ReferenceEquals(this, obj))
-                return true;
-            if (obj.GetType() != GetType())
-                return false;
-            return Equals((SubscriptionsForType)obj);
-        }
+    public override bool Equals(object? obj)
+    {
+        if (ReferenceEquals(null, obj))
+            return false;
+        if (ReferenceEquals(this, obj))
+            return true;
+        if (obj.GetType() != GetType())
+            return false;
+        return Equals((SubscriptionsForType)obj);
+    }
 
 
-        public override int GetHashCode()
+    public override int GetHashCode()
+    {
+        unchecked
         {
         {
-            unchecked
-            {
-                return (MessageTypeId.GetHashCode() * 397) ^ (BindingKeys?.GetHashCode() ?? 0);
-            }
+            return (MessageTypeId.GetHashCode() * 397) ^ (BindingKeys?.GetHashCode() ?? 0);
         }
         }
     }
     }
 }
 }

+ 22 - 22
src/Abc.Zebus/Directory/UnregisterPeerCommand.cs

@@ -2,32 +2,32 @@
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class UnregisterPeerCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = false)]
-        public readonly string? PeerEndPoint;
+[ProtoContract, Transient]
+public class UnregisterPeerCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        [ProtoMember(3, IsRequired = false)]
-        public readonly DateTime? TimestampUtc;
+    [ProtoMember(2, IsRequired = false)]
+    public readonly string? PeerEndPoint;
 
 
-        public UnregisterPeerCommand(Peer peer, DateTime? timestampUtc = null)
-            : this(peer.Id, peer.EndPoint, timestampUtc)
-        {
-        }
+    [ProtoMember(3, IsRequired = false)]
+    public readonly DateTime? TimestampUtc;
 
 
-        public UnregisterPeerCommand(PeerId peerId, string? peerEndPoint, DateTime? timestampUtc = null)
-        {
-            PeerId = peerId;
-            PeerEndPoint = peerEndPoint;
-            TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
-        }
+    public UnregisterPeerCommand(Peer peer, DateTime? timestampUtc = null)
+        : this(peer.Id, peer.EndPoint, timestampUtc)
+    {
+    }
 
 
-        public override string ToString() => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public UnregisterPeerCommand(PeerId peerId, string? peerEndPoint, DateTime? timestampUtc = null)
+    {
+        PeerId = peerId;
+        PeerEndPoint = peerEndPoint;
+        TimestampUtc = timestampUtc ?? SystemDateTime.UtcNow;
     }
     }
+
+    public override string ToString()
+        => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
 }
 }

+ 19 - 19
src/Abc.Zebus/Directory/UpdatePeerSubscriptionsCommand.cs

@@ -2,27 +2,27 @@
 using System.Linq;
 using System.Linq;
 using ProtoBuf;
 using ProtoBuf;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class UpdatePeerSubscriptionsCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly Subscription[] Subscriptions;
+[ProtoContract, Transient]
+public class UpdatePeerSubscriptionsCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        [ProtoMember(3, IsRequired = false)]
-        public readonly DateTime? TimestampUtc;
+    [ProtoMember(2, IsRequired = true)]
+    public readonly Subscription[] Subscriptions;
 
 
-        public UpdatePeerSubscriptionsCommand(PeerId peerId, Subscription[] subscriptions, DateTime logicalClock)
-        {
-            PeerId = peerId;
-            Subscriptions = subscriptions;
-            TimestampUtc = logicalClock;
-        }
+    [ProtoMember(3, IsRequired = false)]
+    public readonly DateTime? TimestampUtc;
 
 
-        public override string ToString() => $"PeerId: {PeerId}, TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}, Subscriptions: [{string.Join(", ", Subscriptions.AsEnumerable())}]";
+    public UpdatePeerSubscriptionsCommand(PeerId peerId, Subscription[] subscriptions, DateTime logicalClock)
+    {
+        PeerId = peerId;
+        Subscriptions = subscriptions;
+        TimestampUtc = logicalClock;
     }
     }
-}
+
+    public override string ToString()
+        => $"PeerId: {PeerId}, TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}, Subscriptions: [{string.Join(", ", Subscriptions.AsEnumerable())}]";
+}

+ 19 - 19
src/Abc.Zebus/Directory/UpdatePeerSubscriptionsForTypesCommand.cs

@@ -1,27 +1,27 @@
 using ProtoBuf;
 using ProtoBuf;
 using System;
 using System;
 
 
-namespace Abc.Zebus.Directory
-{
-    [ProtoContract, Transient]
-    public class UpdatePeerSubscriptionsForTypesCommand : ICommand
-    {
-        [ProtoMember(1, IsRequired = true)]
-        public readonly PeerId PeerId;
+namespace Abc.Zebus.Directory;
 
 
-        [ProtoMember(2, IsRequired = true)]
-        public readonly SubscriptionsForType[] SubscriptionsForTypes;
+[ProtoContract, Transient]
+public class UpdatePeerSubscriptionsForTypesCommand : ICommand
+{
+    [ProtoMember(1, IsRequired = true)]
+    public readonly PeerId PeerId;
 
 
-        [ProtoMember(3, IsRequired = false)]
-        public readonly DateTime TimestampUtc;
+    [ProtoMember(2, IsRequired = true)]
+    public readonly SubscriptionsForType[] SubscriptionsForTypes;
 
 
-        public UpdatePeerSubscriptionsForTypesCommand(PeerId peerId, DateTime timestampUtc, params SubscriptionsForType[] subscriptionsForTypes)
-        {
-            PeerId = peerId;
-            SubscriptionsForTypes = subscriptionsForTypes;
-            TimestampUtc = timestampUtc;
-        }
+    [ProtoMember(3, IsRequired = false)]
+    public readonly DateTime TimestampUtc;
 
 
-        public override string ToString() => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+    public UpdatePeerSubscriptionsForTypesCommand(PeerId peerId, DateTime timestampUtc, params SubscriptionsForType[] subscriptionsForTypes)
+    {
+        PeerId = peerId;
+        SubscriptionsForTypes = subscriptionsForTypes;
+        TimestampUtc = timestampUtc;
     }
     }
-}
+
+    public override string ToString()
+        => $"{PeerId} TimestampUtc: {TimestampUtc:yyyy-MM-dd HH:mm:ss.fff}";
+}

+ 50 - 51
src/Abc.Zebus/Dispatch/AsyncMessageHandlerInvoker.cs

@@ -4,72 +4,71 @@ using System.Reflection;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using StructureMap;
 using StructureMap;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class AsyncMessageHandlerInvoker : MessageHandlerInvoker
 {
 {
-    public class AsyncMessageHandlerInvoker : MessageHandlerInvoker
-    {
-        private readonly Func<object, IMessage, Task> _handleAction;
+    private readonly Func<object, IMessage, Task> _handleAction;
 
 
-        public AsyncMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType)
-            : this(container, handlerType, messageType, MessageHandlerInvokerSubscriber.FromAttributes(handlerType))
-        {
-        }
+    public AsyncMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType)
+        : this(container, handlerType, messageType, MessageHandlerInvokerSubscriber.FromAttributes(handlerType))
+    {
+    }
 
 
-        public AsyncMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
-            : base(container, handlerType, messageType, subscriber)
-        {
-            _handleAction = GenerateHandleAction(handlerType, messageType);
-        }
+    public AsyncMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
+        : base(container, handlerType, messageType, subscriber)
+    {
+        _handleAction = GenerateHandleAction(handlerType, messageType);
+    }
 
 
-        public override MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Asynchronous;
+    public override MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Asynchronous;
 
 
-        public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
-        {
-            throw new NotSupportedException($"{nameof(InvokeMessageHandler)} is not supported in Asynchronous mode");
-        }
+    public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        throw new NotSupportedException($"{nameof(InvokeMessageHandler)} is not supported in Asynchronous mode");
+    }
 
 
-        public override Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+    public override Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+    {
+        try
         {
         {
-            try
+            var handler = CreateHandler(invocation.Context);
+            using (invocation.SetupForInvocation(handler))
             {
             {
-                var handler = CreateHandler(invocation.Context);
-                using (invocation.SetupForInvocation(handler))
-                {
-                    return _handleAction(handler, invocation.Messages[0]);
-                }
-            }
-            catch (Exception ex)
-            {
-                return Task.FromException(ex);
+                return _handleAction(handler, invocation.Messages[0]);
             }
             }
         }
         }
-
-        private static Func<object, IMessage, Task> GenerateHandleAction(Type handlerType, Type messageType)
+        catch (Exception ex)
         {
         {
-            var methodInfo = GetHandleMethodOrThrow(handlerType, messageType);
+            return Task.FromException(ex);
+        }
+    }
 
 
-            var o = Expression.Parameter(typeof(object), "o");
-            var m = Expression.Parameter(typeof(IMessage), "m");
-            var body = Expression.Call(Expression.Convert(o, handlerType), methodInfo, Expression.Convert(m, messageType));
-            var lambda = Expression.Lambda(typeof(Func<object, IMessage, Task>), body, o, m);
+    private static Func<object, IMessage, Task> GenerateHandleAction(Type handlerType, Type messageType)
+    {
+        var methodInfo = GetHandleMethodOrThrow(handlerType, messageType);
 
 
-            return (Func<object, IMessage, Task>)lambda.Compile();
-        }
+        var o = Expression.Parameter(typeof(object), "o");
+        var m = Expression.Parameter(typeof(IMessage), "m");
+        var body = Expression.Call(Expression.Convert(o, handlerType), methodInfo, Expression.Convert(m, messageType));
+        var lambda = Expression.Lambda(typeof(Func<object, IMessage, Task>), body, o, m);
+
+        return (Func<object, IMessage, Task>)lambda.Compile();
+    }
 
 
-        private static MethodInfo GetHandleMethodOrThrow(Type handlerType, Type messageType)
+    private static MethodInfo GetHandleMethodOrThrow(Type handlerType, Type messageType)
+    {
+        try
         {
         {
-            try
-            {
-                var interfaceType = typeof(IAsyncMessageHandler<>).MakeGenericType(messageType);
-                var interfaceMethod = interfaceType.GetMethod(nameof(IAsyncMessageHandler<IMessage>.Handle), new[] { messageType });
-                var interfaceMap = handlerType.GetInterfaceMap(interfaceType);
-                var handleIndex = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
-                return interfaceMap.TargetMethods[handleIndex];
-            }
-            catch (ArgumentException ex)
-            {
-                throw new ArgumentException($"The given handler type ({handlerType.Name}) is not an {nameof(IAsyncMessageHandler<IMessage>)}<{messageType.Name}>", ex);
-            }
+            var interfaceType = typeof(IAsyncMessageHandler<>).MakeGenericType(messageType);
+            var interfaceMethod = interfaceType.GetMethod(nameof(IAsyncMessageHandler<IMessage>.Handle), new[] { messageType });
+            var interfaceMap = handlerType.GetInterfaceMap(interfaceType);
+            var handleIndex = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
+            return interfaceMap.TargetMethods[handleIndex];
+        }
+        catch (ArgumentException ex)
+        {
+            throw new ArgumentException($"The given handler type ({handlerType.Name}) is not an {nameof(IAsyncMessageHandler<IMessage>)}<{messageType.Name}>", ex);
         }
         }
     }
     }
 }
 }

+ 51 - 52
src/Abc.Zebus/Dispatch/BatchedMessageHandlerInvoker.cs

@@ -5,73 +5,72 @@ using System.Linq.Expressions;
 using System.Reflection;
 using System.Reflection;
 using StructureMap;
 using StructureMap;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class BatchedMessageHandlerInvoker : MessageHandlerInvoker
 {
 {
-    public class BatchedMessageHandlerInvoker : MessageHandlerInvoker
+    private static readonly MethodInfo _castMethodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast))!;
+    private static readonly MethodInfo _toListMethodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.ToList))!;
+    private readonly Action<object, IList<IMessage>> _handleAction;
+
+    public BatchedMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType)
+        : this(container, handlerType, messageType, MessageHandlerInvokerSubscriber.FromAttributes(handlerType))
     {
     {
-        private static readonly MethodInfo _castMethodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast))!;
-        private static readonly MethodInfo _toListMethodInfo = typeof(Enumerable).GetMethod(nameof(Enumerable.ToList))!;
-        private readonly Action<object, IList<IMessage>> _handleAction;
+    }
 
 
-        public BatchedMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType)
-            : this(container, handlerType, messageType, MessageHandlerInvokerSubscriber.FromAttributes(handlerType))
-        {
-        }
+    public BatchedMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
+        : base(container, handlerType, messageType, subscriber)
+    {
+        _handleAction = GenerateHandleAction(handlerType, messageType);
+    }
 
 
-        public BatchedMessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
-            : base(container, handlerType, messageType, subscriber)
+    public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        var handler = CreateHandler(invocation.Context);
+        using (invocation.SetupForInvocation(handler))
         {
         {
-            _handleAction = GenerateHandleAction(handlerType, messageType);
+            _handleAction(handler, invocation.Messages);
         }
         }
+    }
 
 
-        public override void InvokeMessageHandler(IMessageHandlerInvocation invocation)
-        {
-            var handler = CreateHandler(invocation.Context);
-            using (invocation.SetupForInvocation(handler))
-            {
-                _handleAction(handler, invocation.Messages);
-            }
-        }
+    public override bool CanMergeWith(IMessageHandlerInvoker other)
+    {
+        return other is BatchedMessageHandlerInvoker otherBatchedInvoker
+               && otherBatchedInvoker.MessageHandlerType == MessageHandlerType
+               && otherBatchedInvoker.MessageType == MessageType;
+    }
 
 
-        public override bool CanMergeWith(IMessageHandlerInvoker other)
-        {
-            return other is BatchedMessageHandlerInvoker otherBatchedInvoker
-                   && otherBatchedInvoker.MessageHandlerType == MessageHandlerType
-                   && otherBatchedInvoker.MessageType == MessageType;
-        }
+    private static Action<object, IList<IMessage>> GenerateHandleAction(Type handlerType, Type messageType)
+    {
+        var handleMethod = GetHandleMethodOrThrow(handlerType, messageType);
+        ThrowIfAsyncVoid(handlerType, handleMethod);
 
 
-        private static Action<object, IList<IMessage>> GenerateHandleAction(Type handlerType, Type messageType)
-        {
-            var handleMethod = GetHandleMethodOrThrow(handlerType, messageType);
-            ThrowIfAsyncVoid(handlerType, handleMethod);
+        var handler = Expression.Parameter(typeof(object), "handler");
+        var messages = Expression.Parameter(typeof(IList<IMessage>), "messages");
 
 
-            var handler = Expression.Parameter(typeof(object), "handler");
-            var messages = Expression.Parameter(typeof(IList<IMessage>), "messages");
+        var castMethod = _castMethodInfo.MakeGenericMethod(messageType);
+        var toListMethod = _toListMethodInfo.MakeGenericMethod(messageType);
+        var messagesList = Expression.Call(toListMethod, Expression.Call(castMethod, messages));
+        var body = Expression.Call(Expression.Convert(handler, handlerType), handleMethod, messagesList);
 
 
-            var castMethod = _castMethodInfo.MakeGenericMethod(messageType);
-            var toListMethod = _toListMethodInfo.MakeGenericMethod(messageType);
-            var messagesList = Expression.Call(toListMethod, Expression.Call(castMethod, messages));
-            var body = Expression.Call(Expression.Convert(handler, handlerType), handleMethod, messagesList);
+        var lambda = Expression.Lambda(typeof(Action<object, IList<IMessage>>), body, handler, messages);
 
 
-            var lambda = Expression.Lambda(typeof(Action<object, IList<IMessage>>), body, handler, messages);
+        return (Action<object, IList<IMessage>>)lambda.Compile();
+    }
 
 
-            return (Action<object, IList<IMessage>>)lambda.Compile();
+    private static MethodInfo GetHandleMethodOrThrow(Type handlerType, Type messageType)
+    {
+        try
+        {
+            var interfaceType = typeof(IBatchedMessageHandler<>).MakeGenericType(messageType);
+            var interfaceMethod = interfaceType.GetMethod(nameof(IBatchedMessageHandler<IEvent>.Handle), new[] { typeof(IList<>).MakeGenericType(messageType) });
+            var interfaceMap = handlerType.GetInterfaceMap(interfaceType);
+            var handleIndex = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
+            return interfaceMap.TargetMethods[handleIndex];
         }
         }
-
-        private static MethodInfo GetHandleMethodOrThrow(Type handlerType, Type messageType)
+        catch (ArgumentException ex)
         {
         {
-            try
-            {
-                var interfaceType = typeof(IBatchedMessageHandler<>).MakeGenericType(messageType);
-                var interfaceMethod = interfaceType.GetMethod(nameof(IBatchedMessageHandler<IEvent>.Handle), new[] { typeof(IList<>).MakeGenericType(messageType) });
-                var interfaceMap = handlerType.GetInterfaceMap(interfaceType);
-                var handleIndex = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
-                return interfaceMap.TargetMethods[handleIndex];
-            }
-            catch (ArgumentException ex)
-            {
-                throw new ArgumentException($"The given handler type ({handlerType.Name}) is not an {nameof(IBatchedMessageHandler<IEvent>)}<{messageType.Name}>", ex);
-            }
+            throw new ArgumentException($"The given handler type ({handlerType.Name}) is not an {nameof(IBatchedMessageHandler<IEvent>)}<{messageType.Name}>", ex);
         }
         }
     }
     }
 }
 }

+ 290 - 291
src/Abc.Zebus/Dispatch/DispatchQueue.cs

@@ -7,390 +7,389 @@ using Abc.Zebus.Util;
 using Abc.Zebus.Util.Collections;
 using Abc.Zebus.Util.Collections;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class DispatchQueue : IDisposable
 {
 {
-    public class DispatchQueue : IDisposable
-    {
-        [ThreadStatic]
-        private static string? _currentDispatchQueueName;
+    [ThreadStatic]
+    private static string? _currentDispatchQueueName;
 
 
-        private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(DispatchQueue));
+    private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(DispatchQueue));
 
 
-        private readonly IPipeManager _pipeManager;
-        private readonly int _batchSize;
-        private FlushableBlockingCollection<Entry> _queue = new FlushableBlockingCollection<Entry>();
-        private Thread? _thread;
-        private volatile bool _isRunning;
-        private int _asyncInvocationsCount;
-        private int _hasCompletedAsyncInvocationsSinceLastWait;
+    private readonly IPipeManager _pipeManager;
+    private readonly int _batchSize;
+    private FlushableBlockingCollection<Entry> _queue = new();
+    private Thread? _thread;
+    private volatile bool _isRunning;
+    private int _asyncInvocationsCount;
+    private int _hasCompletedAsyncInvocationsSinceLastWait;
 
 
-        public string Name { get; }
+    public string Name { get; }
 
 
-        private bool IsCurrentDispatchQueue => _currentDispatchQueueName == Name;
+    private bool IsCurrentDispatchQueue => _currentDispatchQueueName == Name;
 
 
-        internal SynchronizationContext SynchronizationContext { get; }
+    internal SynchronizationContext SynchronizationContext { get; }
 
 
-        public DispatchQueue(IPipeManager pipeManager, int batchSize, string name)
-        {
-            _pipeManager = pipeManager;
-            _batchSize = batchSize;
-            Name = name;
+    public DispatchQueue(IPipeManager pipeManager, int batchSize, string name)
+    {
+        _pipeManager = pipeManager;
+        _batchSize = batchSize;
+        Name = name;
 
 
-            SynchronizationContext = new DispatchQueueSynchronizationContext(this);
-        }
+        SynchronizationContext = new DispatchQueueSynchronizationContext(this);
+    }
 
 
-        public bool IsRunning => _isRunning;
+    public bool IsRunning => _isRunning;
 
 
-        public int QueueLength => _queue.Count;
+    public int QueueLength => _queue.Count;
 
 
-        public void Dispose()
-        {
-            Stop();
-        }
+    public void Dispose()
+    {
+        Stop();
+    }
 
 
-        public void Enqueue(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
-        {
-            _queue.Add(new Entry(dispatch, invoker));
-        }
+    public void Enqueue(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+    {
+        _queue.Add(new Entry(dispatch, invoker));
+    }
 
 
-        private void Enqueue(Action action)
-        {
-            _queue.Add(new Entry(action));
-        }
+    private void Enqueue(Action action)
+    {
+        _queue.Add(new Entry(action));
+    }
 
 
-        public void Start()
-        {
-            if (_isRunning)
-                return;
+    public void Start()
+    {
+        if (_isRunning)
+            return;
 
 
-            _isRunning = true;
-            _thread = new Thread(ThreadProc)
-            {
-                IsBackground = true,
-                Name = $"{Name}.DispatchThread",
-            };
+        _isRunning = true;
+        _thread = new Thread(ThreadProc)
+        {
+            IsBackground = true,
+            Name = $"{Name}.DispatchThread",
+        };
 
 
-            _thread.Start();
-            _logger.LogInformation($"{Name} started");
-        }
+        _thread.Start();
+        _logger.LogInformation($"{Name} started");
+    }
 
 
-        public void Stop()
-        {
-            if (!_isRunning)
-                return;
+    public void Stop()
+    {
+        if (!_isRunning)
+            return;
 
 
-            _isRunning = false;
+        _isRunning = false;
 
 
-            while (WaitUntilAllMessagesAreProcessed())
-                Thread.Sleep(1);
+        while (WaitUntilAllMessagesAreProcessed())
+            Thread.Sleep(1);
 
 
-            _queue.CompleteAdding();
+        _queue.CompleteAdding();
 
 
-            _thread?.Join();
-            _thread = null;
+        _thread?.Join();
+        _thread = null;
 
 
-            _queue = new FlushableBlockingCollection<Entry>();
-            _logger.LogInformation($"{Name} stopped");
-        }
+        _queue = new FlushableBlockingCollection<Entry>();
+        _logger.LogInformation($"{Name} stopped");
+    }
 
 
-        private void ThreadProc()
+    private void ThreadProc()
+    {
+        _currentDispatchQueueName = Name;
+        try
         {
         {
-            _currentDispatchQueueName = Name;
-            try
-            {
-                _logger.LogInformation($"{Name} processing started");
-                var batch = new Batch(_batchSize);
-
-                foreach (var entries in _queue.GetConsumingEnumerable(_batchSize))
-                {
-                    ProcessEntries(entries, batch);
-                }
+            _logger.LogInformation($"{Name} processing started");
+            var batch = new Batch(_batchSize);
 
 
-                _logger.LogInformation($"{Name} processing stopped");
-            }
-            finally
+            foreach (var entries in _queue.GetConsumingEnumerable(_batchSize))
             {
             {
-                _currentDispatchQueueName = null;
+                ProcessEntries(entries, batch);
             }
             }
-        }
 
 
-        private void ProcessEntries(List<Entry> entries, Batch batch)
+            _logger.LogInformation($"{Name} processing stopped");
+        }
+        finally
         {
         {
-            foreach (var entry in entries)
-            {
-                if (!batch.CanAdd(entry))
-                    RunBatch(batch);
+            _currentDispatchQueueName = null;
+        }
+    }
 
 
-                if (entry.Action != null)
-                    RunAction(entry.Action);
-                else
-                    batch.Add(entry);
-            }
+    private void ProcessEntries(List<Entry> entries, Batch batch)
+    {
+        foreach (var entry in entries)
+        {
+            if (!batch.CanAdd(entry))
+                RunBatch(batch);
 
 
-            RunBatch(batch);
+            if (entry.Action != null)
+                RunAction(entry.Action);
+            else
+                batch.Add(entry);
         }
         }
 
 
-        private void RunAction(Action action)
+        RunBatch(batch);
+    }
+
+    private void RunAction(Action action)
+    {
+        try
         {
         {
-            try
-            {
-                SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
-                action();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error running action");
-            }
-            finally
-            {
-                LocalDispatch.Reset();
-            }
+            SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
+            action();
         }
         }
-
-        private void RunSingle(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+        catch (Exception ex)
         {
         {
-            if (!_isRunning)
-                return;
+            _logger.LogError(ex, "Error running action");
+        }
+        finally
+        {
+            LocalDispatch.Reset();
+        }
+    }
 
 
-            var batch = new Batch(1);
-            batch.Add(new Entry(dispatch, invoker));
+    private void RunSingle(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+    {
+        if (!_isRunning)
+            return;
 
 
-            RunBatch(batch);
-        }
+        var batch = new Batch(1);
+        batch.Add(new Entry(dispatch, invoker));
+
+        RunBatch(batch);
+    }
 
 
-        private void RunBatch(Batch batch)
+    private void RunBatch(Batch batch)
+    {
+        if (batch.IsEmpty)
+            return;
+
+        try
         {
         {
-            if (batch.IsEmpty)
+            if (!_isRunning)
                 return;
                 return;
 
 
-            try
+            switch (batch.FirstEntry.Invoker!.Mode)
             {
             {
-                if (!_isRunning)
-                    return;
+                case MessageHandlerInvokerMode.Synchronous:
+                {
+                    SynchronizationContext.SetSynchronizationContext(null);
+                    var invocation = _pipeManager.BuildPipeInvocation(batch.FirstEntry.Invoker, batch.Messages, batch.FirstEntry.Dispatch!.Context);
+                    invocation.Run();
+                    batch.SetHandled(null);
+                    break;
+                }
 
 
-                switch (batch.FirstEntry.Invoker!.Mode)
+                case MessageHandlerInvokerMode.Asynchronous:
                 {
                 {
-                    case MessageHandlerInvokerMode.Synchronous:
-                    {
-                        SynchronizationContext.SetSynchronizationContext(null);
-                        var invocation = _pipeManager.BuildPipeInvocation(batch.FirstEntry.Invoker, batch.Messages, batch.FirstEntry.Dispatch!.Context);
-                        invocation.Run();
-                        batch.SetHandled(null);
-                        break;
-                    }
-
-                    case MessageHandlerInvokerMode.Asynchronous:
-                    {
-                        SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
-                        var asyncBatch = batch.Clone();
-                        var invocation = _pipeManager.BuildPipeInvocation(asyncBatch.FirstEntry.Invoker!, asyncBatch.Messages, asyncBatch.FirstEntry.Dispatch!.Context);
-                        Interlocked.Increment(ref _asyncInvocationsCount);
-                        invocation.RunAsync().ContinueWith(task => OnAsyncBatchCompleted(task, asyncBatch), TaskContinuationOptions.ExecuteSynchronously);
-                        break;
-                    }
-
-                    default:
-                        throw new ArgumentOutOfRangeException();
+                    SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
+                    var asyncBatch = batch.Clone();
+                    var invocation = _pipeManager.BuildPipeInvocation(asyncBatch.FirstEntry.Invoker!, asyncBatch.Messages, asyncBatch.FirstEntry.Dispatch!.Context);
+                    Interlocked.Increment(ref _asyncInvocationsCount);
+                    invocation.RunAsync().ContinueWith(task => OnAsyncBatchCompleted(task, asyncBatch), TaskContinuationOptions.ExecuteSynchronously);
+                    break;
                 }
                 }
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error running batch");
-                batch.SetHandled(ex);
-            }
-            finally
-            {
-                batch.Clear();
-                LocalDispatch.Reset();
+
+                default:
+                    throw new ArgumentOutOfRangeException();
             }
             }
         }
         }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error running batch");
+            batch.SetHandled(ex);
+        }
+        finally
+        {
+            batch.Clear();
+            LocalDispatch.Reset();
+        }
+    }
 
 
-        private void OnAsyncBatchCompleted(Task task, Batch asyncBatch)
+    private void OnAsyncBatchCompleted(Task task, Batch asyncBatch)
+    {
+        try
         {
         {
-            try
-            {
-                var exception = task.IsFaulted
-                    ? task.Exception?.InnerException ?? new Exception("Task failed")
-                    : task.IsCanceled
-                        ? task.Exception?.InnerException ?? new TaskCanceledException()
-                        : null;
+            var exception = task.IsFaulted
+                ? task.Exception?.InnerException ?? new Exception("Task failed")
+                : task.IsCanceled
+                    ? task.Exception?.InnerException ?? new TaskCanceledException()
+                    : null;
 
 
-                if (exception != null)
-                    _logger.LogError(exception, "Error running async batch");
+            if (exception != null)
+                _logger.LogError(exception, "Error running async batch");
 
 
-                asyncBatch.SetHandled(exception);
-            }
-            finally
-            {
-                _hasCompletedAsyncInvocationsSinceLastWait = 1;
-                Interlocked.Decrement(ref _asyncInvocationsCount);
-            }
+            asyncBatch.SetHandled(exception);
         }
         }
-
-        public virtual int Purge()
+        finally
         {
         {
-            var flushedEntries = _queue.Flush();
-            return flushedEntries.Count;
+            _hasCompletedAsyncInvocationsSinceLastWait = 1;
+            Interlocked.Decrement(ref _asyncInvocationsCount);
         }
         }
+    }
 
 
-        /// <summary>
-        /// Waits until the dispatch queue is empty and no messages are currently being processed
-        /// </summary>
-        /// <returns>
-        /// true if the dispatch queue has processed messages since the last call to this function
-        /// </returns>
-        public bool WaitUntilAllMessagesAreProcessed()
-        {
-            if (_thread is null)
-                return false;
+    public virtual int Purge()
+    {
+        var flushedEntries = _queue.Flush();
+        return flushedEntries.Count;
+    }
 
 
-            bool continueWait, hasChanged = false;
+    /// <summary>
+    /// Waits until the dispatch queue is empty and no messages are currently being processed
+    /// </summary>
+    /// <returns>
+    /// true if the dispatch queue has processed messages since the last call to this function
+    /// </returns>
+    public bool WaitUntilAllMessagesAreProcessed()
+    {
+        if (_thread is null)
+            return false;
 
 
-            do
-            {
-                continueWait = false;
+        bool continueWait, hasChanged = false;
 
 
-                while (Volatile.Read(ref _asyncInvocationsCount) > 0)
-                {
-                    continueWait = true;
-                    Thread.Sleep(1);
-                }
+        do
+        {
+            continueWait = false;
 
 
-                if (Interlocked.Exchange(ref _hasCompletedAsyncInvocationsSinceLastWait, 0) != 0)
-                    continueWait = true;
+            while (Volatile.Read(ref _asyncInvocationsCount) > 0)
+            {
+                continueWait = true;
+                Thread.Sleep(1);
+            }
 
 
-                if (_queue.WaitUntilIsEmpty())
-                    continueWait = true;
+            if (Interlocked.Exchange(ref _hasCompletedAsyncInvocationsSinceLastWait, 0) != 0)
+                continueWait = true;
 
 
-                if (continueWait)
-                    hasChanged = true;
-            } while (continueWait);
+            if (_queue.WaitUntilIsEmpty())
+                continueWait = true;
 
 
-            return hasChanged;
-        }
+            if (continueWait)
+                hasChanged = true;
+        } while (continueWait);
 
 
-        public void RunOrEnqueue(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+        return hasChanged;
+    }
+
+    public void RunOrEnqueue(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+    {
+        if (dispatch.ShouldRunSynchronously || IsCurrentDispatchQueue)
         {
         {
-            if (dispatch.ShouldRunSynchronously || IsCurrentDispatchQueue)
-            {
-                RunSingle(dispatch, invoker);
-            }
-            else
-            {
-                dispatch.BeforeEnqueue();
-                Enqueue(dispatch, invoker);
-            }
+            RunSingle(dispatch, invoker);
         }
         }
-
-        // for unit tests
-        internal static string? GetCurrentDispatchQueueName()
+        else
         {
         {
-            return _currentDispatchQueueName;
+            dispatch.BeforeEnqueue();
+            Enqueue(dispatch, invoker);
         }
         }
+    }
 
 
-        // for unit tests
-        internal static IDisposable SetCurrentDispatchQueueName(string queueName)
-        {
-            _currentDispatchQueueName = queueName;
+    // for unit tests
+    internal static string? GetCurrentDispatchQueueName()
+    {
+        return _currentDispatchQueueName;
+    }
+
+    // for unit tests
+    internal static IDisposable SetCurrentDispatchQueueName(string queueName)
+    {
+        _currentDispatchQueueName = queueName;
+
+        return new DisposableAction(() => _currentDispatchQueueName = null);
+    }
 
 
-            return new DisposableAction(() => _currentDispatchQueueName = null);
+    private readonly struct Entry
+    {
+        public Entry(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+        {
+            Dispatch = dispatch;
+            Invoker = invoker;
+            Action = null;
         }
         }
 
 
-        private readonly struct Entry
+        public Entry(Action action)
         {
         {
-            public Entry(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
-            {
-                Dispatch = dispatch;
-                Invoker = invoker;
-                Action = null;
-            }
+            Dispatch = null;
+            Invoker = null;
+            Action = action;
+        }
 
 
-            public Entry(Action action)
-            {
-                Dispatch = null;
-                Invoker = null;
-                Action = action;
-            }
+        public readonly MessageDispatch? Dispatch;
+        public readonly IMessageHandlerInvoker? Invoker;
+        public readonly Action? Action;
+    }
 
 
-            public readonly MessageDispatch? Dispatch;
-            public readonly IMessageHandlerInvoker? Invoker;
-            public readonly Action? Action;
-        }
+    private class Batch
+    {
+        public readonly List<Entry> Entries;
+        public readonly List<IMessage> Messages;
 
 
-        private class Batch
+        public Batch(int capacity)
         {
         {
-            public readonly List<Entry> Entries;
-            public readonly List<IMessage> Messages;
-
-            public Batch(int capacity)
-            {
-                Entries = new List<Entry>(capacity);
-                Messages = new List<IMessage>(capacity);
-            }
+            Entries = new List<Entry>(capacity);
+            Messages = new List<IMessage>(capacity);
+        }
 
 
-            public Entry FirstEntry => Entries[0];
-            public bool IsEmpty => Entries.Count == 0;
+        public Entry FirstEntry => Entries[0];
+        public bool IsEmpty => Entries.Count == 0;
 
 
-            public void Add(Entry entry)
-            {
-                Entries.Add(entry);
-                Messages.Add(entry.Dispatch!.Message);
-            }
+        public void Add(Entry entry)
+        {
+            Entries.Add(entry);
+            Messages.Add(entry.Dispatch!.Message);
+        }
 
 
-            public void SetHandled(Exception? error)
+        public void SetHandled(Exception? error)
+        {
+            foreach (var entry in Entries)
             {
             {
-                foreach (var entry in Entries)
+                try
                 {
                 {
-                    try
-                    {
-                        entry.Dispatch!.SetHandled(entry.Invoker!, error);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, $"Unable to run dispatch continuation, MessageType: {entry.Invoker!.MessageType.Name}, HandlerType: {entry.Invoker!.MessageHandlerType.Name}, HandlerError: {error}");
-                    }
+                    entry.Dispatch!.SetHandled(entry.Invoker!, error);
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, $"Unable to run dispatch continuation, MessageType: {entry.Invoker!.MessageType.Name}, HandlerType: {entry.Invoker!.MessageHandlerType.Name}, HandlerError: {error}");
                 }
                 }
             }
             }
+        }
 
 
-            public void Clear()
-            {
-                Entries.Clear();
-                Messages.Clear();
-            }
+        public void Clear()
+        {
+            Entries.Clear();
+            Messages.Clear();
+        }
 
 
-            public Batch Clone()
-            {
-                var clone = new Batch(Entries.Count);
-                clone.Entries.AddRange(Entries);
-                clone.Messages.AddRange(Messages);
-                return clone;
-            }
+        public Batch Clone()
+        {
+            var clone = new Batch(Entries.Count);
+            clone.Entries.AddRange(Entries);
+            clone.Messages.AddRange(Messages);
+            return clone;
+        }
 
 
-            public bool CanAdd(Entry entry)
-            {
-                if (entry.Action != null)
-                    return false;
+        public bool CanAdd(Entry entry)
+        {
+            if (entry.Action != null)
+                return false;
 
 
-                if (IsEmpty)
-                    return true;
+            if (IsEmpty)
+                return true;
 
 
-                return entry.Invoker!.CanMergeWith(FirstEntry.Invoker!);
-            }
+            return entry.Invoker!.CanMergeWith(FirstEntry.Invoker!);
         }
         }
+    }
 
 
-        private class DispatchQueueSynchronizationContext : SynchronizationContext
-        {
-            private readonly DispatchQueue _dispatchQueue;
+    private class DispatchQueueSynchronizationContext : SynchronizationContext
+    {
+        private readonly DispatchQueue _dispatchQueue;
 
 
-            public DispatchQueueSynchronizationContext(DispatchQueue dispatchQueue)
-            {
-                _dispatchQueue = dispatchQueue;
-            }
+        public DispatchQueueSynchronizationContext(DispatchQueue dispatchQueue)
+        {
+            _dispatchQueue = dispatchQueue;
+        }
 
 
-            public override void Post(SendOrPostCallback d, object? state)
-            {
-                _dispatchQueue.Enqueue(() => d(state));
-            }
+        public override void Post(SendOrPostCallback d, object? state)
+        {
+            _dispatchQueue.Enqueue(() => d(state));
         }
         }
     }
     }
 }
 }

+ 13 - 15
src/Abc.Zebus/Dispatch/DispatchQueueFactory.cs

@@ -1,22 +1,20 @@
 using Abc.Zebus.Dispatch.Pipes;
 using Abc.Zebus.Dispatch.Pipes;
-using Abc.Zebus.Util;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class DispatchQueueFactory : IDispatchQueueFactory
 {
 {
-    public class DispatchQueueFactory : IDispatchQueueFactory
-    {
-        private readonly IPipeManager _pipeManager;
-        private readonly IBusConfiguration _configuration;
+    private readonly IPipeManager _pipeManager;
+    private readonly IBusConfiguration _configuration;
 
 
-        public DispatchQueueFactory(IPipeManager pipeManager, IBusConfiguration configuration)
-        {
-            _pipeManager = pipeManager;
-            _configuration = configuration;
-        }
+    public DispatchQueueFactory(IPipeManager pipeManager, IBusConfiguration configuration)
+    {
+        _pipeManager = pipeManager;
+        _configuration = configuration;
+    }
 
 
-        public DispatchQueue Create(string queueName)
-        {
-            return new DispatchQueue(_pipeManager, _configuration.MessagesBatchSize, queueName);
-        }
+    public DispatchQueue Create(string queueName)
+    {
+        return new DispatchQueue(_pipeManager, _configuration.MessagesBatchSize, queueName);
     }
     }
 }
 }

+ 8 - 9
src/Abc.Zebus/Dispatch/DispatchQueueNameAttribute.cs

@@ -1,15 +1,14 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class DispatchQueueNameAttribute : Attribute
 {
 {
-    [AttributeUsage(AttributeTargets.Class)]
-    public class DispatchQueueNameAttribute : Attribute
+    public DispatchQueueNameAttribute(string queueName)
     {
     {
-        public DispatchQueueNameAttribute(string queueName)
-        {
-            QueueName = queueName;
-        }
-
-        public string QueueName { get; }
+        QueueName = queueName;
     }
     }
+
+    public string QueueName { get; }
 }
 }

+ 11 - 12
src/Abc.Zebus/Dispatch/DispatchResult.cs

@@ -1,20 +1,19 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public readonly struct DispatchResult
 {
 {
-    public readonly struct DispatchResult
-    {
-        private static readonly Dictionary<Type, Exception> _noErrors = new Dictionary<Type, Exception>();
-        private readonly IDictionary<Type, Exception> _errors;
+    private static readonly Dictionary<Type, Exception> _noErrors = new();
+    private readonly IDictionary<Type, Exception> _errors;
 
 
-        public DispatchResult(IDictionary<Type, Exception>? errorsByHandlerType)
-        {
-            _errors = errorsByHandlerType ?? _noErrors;
-        }
+    public DispatchResult(IDictionary<Type, Exception>? errorsByHandlerType)
+    {
+        _errors = errorsByHandlerType ?? _noErrors;
+    }
 
 
-        public ICollection<Exception> Errors => _errors.Values;
+    public ICollection<Exception> Errors => _errors.Values;
 
 
-        public ICollection<Type> ErrorHandlerTypes => _errors.Keys;
-    }
+    public ICollection<Type> ErrorHandlerTypes => _errors.Keys;
 }
 }

+ 40 - 41
src/Abc.Zebus/Dispatch/DynamicMessageHandlerInvoker.cs

@@ -5,64 +5,63 @@ using System.Threading.Tasks;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Scan;
 using Abc.Zebus.Scan;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class DynamicMessageHandlerInvoker : IMessageHandlerInvoker
 {
 {
-    public class DynamicMessageHandlerInvoker : IMessageHandlerInvoker
-    {
-        private readonly Action<IMessage> _handler;
-        private readonly List<Func<IMessage, bool>> _predicates;
+    private readonly Action<IMessage> _handler;
+    private readonly List<Func<IMessage, bool>> _predicates;
 
 
-        public DynamicMessageHandlerInvoker(Action<IMessage> handler, Type messageType, ICollection<BindingKey> bindingKeys)
-        {
-            var messageTypeId = new MessageTypeId(messageType);
+    public DynamicMessageHandlerInvoker(Action<IMessage> handler, Type messageType, ICollection<BindingKey> bindingKeys)
+    {
+        var messageTypeId = new MessageTypeId(messageType);
 
 
-            _handler = handler;
-            _predicates = bindingKeys.Select(x => BindingKeyUtil.BuildPredicate(messageTypeId, x)).ToList();
+        _handler = handler;
+        _predicates = bindingKeys.Select(x => BindingKeyUtil.BuildPredicate(messageTypeId, x)).ToList();
 
 
-            MessageType = messageType;
-            MessageTypeId = messageTypeId;
-        }
+        MessageType = messageType;
+        MessageTypeId = messageTypeId;
+    }
 
 
-        public Type MessageHandlerType => typeof(DummyHandler);
-        public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
-        public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
+    public Type MessageHandlerType => typeof(DummyHandler);
+    public string DispatchQueueName => DispatchQueueNameScanner.DefaultQueueName;
+    public MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
 
 
-        public Type MessageType { get; }
-        public MessageTypeId MessageTypeId { get; }
+    public Type MessageType { get; }
+    public MessageTypeId MessageTypeId { get; }
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions() => Enumerable.Empty<Subscription>();
+    public IEnumerable<Subscription> GetStartupSubscriptions() => Enumerable.Empty<Subscription>();
 
 
-        public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    public void InvokeMessageHandler(IMessageHandlerInvocation invocation)
+    {
+        using (invocation.SetupForInvocation())
         {
         {
-            using (invocation.SetupForInvocation())
-            {
-                foreach (var message in invocation.Messages)
-                    _handler(message);
-            }
+            foreach (var message in invocation.Messages)
+                _handler(message);
         }
         }
+    }
 
 
-        public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
-            => throw new NotSupportedException("InvokeMessageHandlerAsync is not supported in Synchronous mode");
+    public Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+        => throw new NotSupportedException("InvokeMessageHandlerAsync is not supported in Synchronous mode");
 
 
-        public bool ShouldHandle(IMessage message)
+    public bool ShouldHandle(IMessage message)
+    {
+        foreach (var predicate in _predicates)
         {
         {
-            foreach (var predicate in _predicates)
-            {
-                if (predicate(message))
-                    return true;
-            }
-
-            return false;
+            if (predicate(message))
+                return true;
         }
         }
 
 
-        public bool CanMergeWith(IMessageHandlerInvoker other) => false;
+        return false;
+    }
+
+    public bool CanMergeWith(IMessageHandlerInvoker other) => false;
 
 
-        private class DummyHandler : IMessageHandler<IMessage>
+    private class DummyHandler : IMessageHandler<IMessage>
+    {
+        public void Handle(IMessage message)
         {
         {
-            public void Handle(IMessage message)
-            {
-                throw new NotSupportedException("This handler is only used to provide the base class with a valid implementation of IMessageHandler and is never actually used");
-            }
+            throw new NotSupportedException("This handler is only used to provide the base class with a valid implementation of IMessageHandler and is never actually used");
         }
         }
     }
     }
 }
 }

+ 5 - 6
src/Abc.Zebus/Dispatch/IDispatchQueueFactory.cs

@@ -1,7 +1,6 @@
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IDispatchQueueFactory
 {
 {
-    public interface IDispatchQueueFactory
-    {
-        DispatchQueue Create(string queueName);
-    }
-}
+    DispatchQueue Create(string queueName);
+}

+ 4 - 5
src/Abc.Zebus/Dispatch/IMessageDispatchFactory.cs

@@ -1,9 +1,8 @@
 using Abc.Zebus.Transport;
 using Abc.Zebus.Transport;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IMessageDispatchFactory
 {
 {
-    public interface IMessageDispatchFactory
-    {
-        MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage);
-    }
+    MessageDispatch? CreateMessageDispatch(TransportMessage transportMessage);
 }
 }

+ 19 - 20
src/Abc.Zebus/Dispatch/IMessageDispatcher.cs

@@ -2,32 +2,31 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Reflection;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IMessageDispatcher
 {
 {
-    public interface IMessageDispatcher
-    {
-        void ConfigureAssemblyFilter(Func<Assembly, bool> assemblyFilter);
-        void ConfigureHandlerFilter(Func<Type, bool> handlerFilter);
-        void ConfigureMessageFilter(Func<Type, bool> messageFilter);
-        event Action MessageHandlerInvokersUpdated;
+    void ConfigureAssemblyFilter(Func<Assembly, bool> assemblyFilter);
+    void ConfigureHandlerFilter(Func<Type, bool> handlerFilter);
+    void ConfigureMessageFilter(Func<Type, bool> messageFilter);
+    event Action MessageHandlerInvokersUpdated;
 
 
-        void LoadMessageHandlerInvokers();
+    void LoadMessageHandlerInvokers();
 
 
-        IEnumerable<MessageTypeId> GetHandledMessageTypes();
-        IEnumerable<IMessageHandlerInvoker> GetMessageHandlerInvokers();
+    IEnumerable<MessageTypeId> GetHandledMessageTypes();
+    IEnumerable<IMessageHandlerInvoker> GetMessageHandlerInvokers();
 
 
-        void Dispatch(MessageDispatch dispatch);
-        void Dispatch(MessageDispatch dispatch, Func<Type, bool> handlerFilter);
+    void Dispatch(MessageDispatch dispatch);
+    void Dispatch(MessageDispatch dispatch, Func<Type, bool> handlerFilter);
 
 
-        void AddInvoker(IMessageHandlerInvoker newEventHandlerInvoker);
-        void RemoveInvoker(IMessageHandlerInvoker eventHandlerInvoker);
+    void AddInvoker(IMessageHandlerInvoker newEventHandlerInvoker);
+    void RemoveInvoker(IMessageHandlerInvoker eventHandlerInvoker);
 
 
-        int Purge();
+    int Purge();
 
 
-        void Stop();
-        void Start();
+    void Stop();
+    void Start();
 
 
-        event Action Starting;
-        event Action Stopping;
-    }
+    event Action Starting;
+    event Action Stopping;
 }
 }

+ 7 - 8
src/Abc.Zebus/Dispatch/IMessageHandlerInvocation.cs

@@ -1,14 +1,13 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IMessageHandlerInvocation
 {
 {
-    public interface IMessageHandlerInvocation
-    {
-        IList<IMessage> Messages { get; }
-        MessageContext Context { get; }
+    IList<IMessage> Messages { get; }
+    MessageContext Context { get; }
 
 
-        IDisposable SetupForInvocation();
-        IDisposable SetupForInvocation(object messageHandler);
-    }
+    IDisposable SetupForInvocation();
+    IDisposable SetupForInvocation(object messageHandler);
 }
 }

+ 13 - 14
src/Abc.Zebus/Dispatch/IMessageHandlerInvoker.cs

@@ -2,20 +2,19 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IMessageHandlerInvoker
 {
 {
-    public interface IMessageHandlerInvoker
-    {
-        Type MessageHandlerType { get; }
-        Type MessageType { get; }
-        MessageTypeId MessageTypeId { get; }
-        string DispatchQueueName { get; }
-        MessageHandlerInvokerMode Mode { get; }
+    Type MessageHandlerType { get; }
+    Type MessageType { get; }
+    MessageTypeId MessageTypeId { get; }
+    string DispatchQueueName { get; }
+    MessageHandlerInvokerMode Mode { get; }
 
 
-        IEnumerable<Subscription> GetStartupSubscriptions();
-        void InvokeMessageHandler(IMessageHandlerInvocation invocation);
-        Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation);
-        bool ShouldHandle(IMessage message);
-        bool CanMergeWith(IMessageHandlerInvoker other);
-    }
+    IEnumerable<Subscription> GetStartupSubscriptions();
+    void InvokeMessageHandler(IMessageHandlerInvocation invocation);
+    Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation);
+    bool ShouldHandle(IMessage message);
+    bool CanMergeWith(IMessageHandlerInvoker other);
 }
 }

+ 5 - 6
src/Abc.Zebus/Dispatch/IProvideDispatchQueueNameForCurrentNamespace.cs

@@ -1,7 +1,6 @@
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public interface IProvideDispatchQueueNameForCurrentNamespace
 {
 {
-    public interface IProvideDispatchQueueNameForCurrentNamespace
-    {
-        string QueueName { get; } 
-    }
-}
+    string QueueName { get; }
+}

+ 14 - 14
src/Abc.Zebus/Dispatch/LocalDispatch.cs

@@ -1,23 +1,23 @@
 using System;
 using System;
 using Abc.Zebus.Util;
 using Abc.Zebus.Util;
 
 
-namespace Abc.Zebus.Dispatch
-{
-    public static class LocalDispatch
-    {
-        [ThreadStatic]
-        private static bool _localDispatchDisabled;
+namespace Abc.Zebus.Dispatch;
 
 
-        public static bool Enabled => !_localDispatchDisabled;
+public static class LocalDispatch
+{
+    [ThreadStatic]
+    private static bool _localDispatchDisabled;
 
 
-        public static IDisposable Disable()
-        {
-            var currentValue = _localDispatchDisabled;
-            _localDispatchDisabled = true;
+    public static bool Enabled => !_localDispatchDisabled;
 
 
-            return new DisposableAction(() => _localDispatchDisabled = currentValue);
-        }
+    public static IDisposable Disable()
+    {
+        var currentValue = _localDispatchDisabled;
+        _localDispatchDisabled = true;
 
 
-        internal static void Reset() => _localDispatchDisabled = false;
+        return new DisposableAction(() => _localDispatchDisabled = currentValue);
     }
     }
+
+    internal static void Reset()
+        => _localDispatchDisabled = false;
 }
 }

+ 50 - 51
src/Abc.Zebus/Dispatch/MessageDispatch.cs

@@ -3,72 +3,71 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using Abc.Zebus.Serialization;
 using Abc.Zebus.Serialization;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class MessageDispatch
 {
 {
-    public class MessageDispatch
-    {
-        private static readonly object _exceptionsLock = new object();
+    private static readonly object _exceptionsLock = new();
 
 
-        private readonly IMessageSerializer _messageSerializer;
-        private readonly Action<MessageDispatch, DispatchResult> _continuation;
-        private Dictionary<Type, Exception>? _exceptions;
-        private int _remainingHandlerCount;
-        private bool _isCloned;
+    private readonly IMessageSerializer _messageSerializer;
+    private readonly Action<MessageDispatch, DispatchResult> _continuation;
+    private Dictionary<Type, Exception>? _exceptions;
+    private int _remainingHandlerCount;
+    private bool _isCloned;
 
 
-        public MessageDispatch(MessageContext context, IMessage message, IMessageSerializer messageSerializer, Action<MessageDispatch, DispatchResult> continuation, bool shouldRunSynchronously = false)
-        {
-            _messageSerializer = messageSerializer;
-            _continuation = continuation;
+    public MessageDispatch(MessageContext context, IMessage message, IMessageSerializer messageSerializer, Action<MessageDispatch, DispatchResult> continuation, bool shouldRunSynchronously = false)
+    {
+        _messageSerializer = messageSerializer;
+        _continuation = continuation;
 
 
-            ShouldRunSynchronously = shouldRunSynchronously;
-            Context = context;
-            Message = message;
-        }
+        ShouldRunSynchronously = shouldRunSynchronously;
+        Context = context;
+        Message = message;
+    }
 
 
-        public bool IsLocal { get; set; }
-        public bool ShouldRunSynchronously { get; }
-        public MessageContext Context { get; }
-        public IMessage Message { get; private set; }
+    public bool IsLocal { get; set; }
+    public bool ShouldRunSynchronously { get; }
+    public MessageContext Context { get; }
+    public IMessage Message { get; private set; }
 
 
-        public void SetIgnored()
-        {
-            _continuation(this, new DispatchResult(null));
-        }
+    public void SetIgnored()
+    {
+        _continuation(this, new DispatchResult(null));
+    }
 
 
-        public void SetHandled(IMessageHandlerInvoker invoker, Exception? error)
-        {
-            if (error != null)
-                AddException(invoker.MessageHandlerType, error);
+    public void SetHandled(IMessageHandlerInvoker invoker, Exception? error)
+    {
+        if (error != null)
+            AddException(invoker.MessageHandlerType, error);
 
 
-            if (Interlocked.Decrement(ref _remainingHandlerCount) == 0)
-                _continuation(this, new DispatchResult(_exceptions));
-        }
+        if (Interlocked.Decrement(ref _remainingHandlerCount) == 0)
+            _continuation(this, new DispatchResult(_exceptions));
+    }
 
 
-        private void AddException(Type messageHandlerType, Exception error)
+    private void AddException(Type messageHandlerType, Exception error)
+    {
+        lock (_exceptionsLock)
         {
         {
-            lock (_exceptionsLock)
-            {
-                if (_exceptions == null)
-                    _exceptions = new Dictionary<Type, Exception>();
+            if (_exceptions == null)
+                _exceptions = new Dictionary<Type, Exception>();
 
 
-                _exceptions[messageHandlerType] = error;
-            }
+            _exceptions[messageHandlerType] = error;
         }
         }
+    }
 
 
-        public void SetHandlerCount(int handlerCount)
-        {
-            _remainingHandlerCount = handlerCount;
-        }
+    public void SetHandlerCount(int handlerCount)
+    {
+        _remainingHandlerCount = handlerCount;
+    }
 
 
-        internal void BeforeEnqueue()
-        {
-            if (!IsLocal || _isCloned)
-                return;
+    internal void BeforeEnqueue()
+    {
+        if (!IsLocal || _isCloned)
+            return;
 
 
-            if (_messageSerializer.TryClone(Message, out var clone))
-                Message = clone;
+        if (_messageSerializer.TryClone(Message, out var clone))
+            Message = clone;
 
 
-            _isCloned = true;
-        }
+        _isCloned = true;
     }
     }
 }
 }

+ 192 - 193
src/Abc.Zebus/Dispatch/MessageDispatcher.cs

@@ -9,272 +9,271 @@ using Abc.Zebus.Scan;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class MessageDispatcher : IMessageDispatcher, IProvideQueueLength
 {
 {
-    public class MessageDispatcher : IMessageDispatcher, IProvideQueueLength
+    private static readonly List<IMessageHandlerInvoker> _emptyInvokers = new();
+    private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(MessageDispatcher));
+
+    private readonly ConcurrentDictionary<string, DispatchQueue> _dispatchQueues = new(StringComparer.OrdinalIgnoreCase);
+    private readonly object _lock = new();
+    private readonly IMessageHandlerInvokerLoader[] _invokerLoaders;
+    private readonly IDispatchQueueFactory _dispatchQueueFactory;
+    private ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>> _invokers = new();
+    private Func<Assembly, bool>? _assemblyFilter;
+    private Func<Type, bool>? _handlerFilter;
+    private Func<Type, bool>? _messageFilter;
+    private Status _status;
+
+    public MessageDispatcher(IMessageHandlerInvokerLoader[] invokerLoaders, IDispatchQueueFactory dispatchQueueFactory)
     {
     {
-        private static readonly List<IMessageHandlerInvoker> _emptyInvokers = new();
-        private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(MessageDispatcher));
-
-        private readonly ConcurrentDictionary<string, DispatchQueue> _dispatchQueues = new(StringComparer.OrdinalIgnoreCase);
-        private readonly object _lock = new();
-        private readonly IMessageHandlerInvokerLoader[] _invokerLoaders;
-        private readonly IDispatchQueueFactory _dispatchQueueFactory;
-        private ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>> _invokers = new();
-        private Func<Assembly, bool>? _assemblyFilter;
-        private Func<Type, bool>? _handlerFilter;
-        private Func<Type, bool>? _messageFilter;
-        private Status _status;
-
-        public MessageDispatcher(IMessageHandlerInvokerLoader[] invokerLoaders, IDispatchQueueFactory dispatchQueueFactory)
-        {
-            _invokerLoaders = invokerLoaders;
-            _dispatchQueueFactory = dispatchQueueFactory;
-        }
+        _invokerLoaders = invokerLoaders;
+        _dispatchQueueFactory = dispatchQueueFactory;
+    }
 
 
-        public event Action? Starting;
-        public event Action? Stopping;
+    public event Action? Starting;
+    public event Action? Stopping;
 
 
-        public void ConfigureAssemblyFilter(Func<Assembly, bool> assemblyFilter) => _assemblyFilter = assemblyFilter;
+    public void ConfigureAssemblyFilter(Func<Assembly, bool> assemblyFilter) => _assemblyFilter = assemblyFilter;
 
 
-        public void ConfigureHandlerFilter(Func<Type, bool> handlerFilter) => _handlerFilter = handlerFilter;
+    public void ConfigureHandlerFilter(Func<Type, bool> handlerFilter) => _handlerFilter = handlerFilter;
 
 
-        public void ConfigureMessageFilter(Func<Type, bool> messageFilter) => _messageFilter = messageFilter;
+    public void ConfigureMessageFilter(Func<Type, bool> messageFilter) => _messageFilter = messageFilter;
 
 
-        public event Action? MessageHandlerInvokersUpdated;
+    public event Action? MessageHandlerInvokersUpdated;
 
 
-        public void LoadMessageHandlerInvokers()
-        {
-            _status = Status.Loaded;
+    public void LoadMessageHandlerInvokers()
+    {
+        _status = Status.Loaded;
 
 
-            _invokers = LoadInvokers();
+        _invokers = LoadInvokers();
 
 
-            LoadDispatchQueues();
+        LoadDispatchQueues();
 
 
-            MessageHandlerInvokersUpdated?.Invoke();
-        }
+        MessageHandlerInvokersUpdated?.Invoke();
+    }
+
+    private ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>> LoadInvokers()
+    {
+        var invokers = new ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>>();
+        var typeSource = CreateTypeSource();
 
 
-        private ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>> LoadInvokers()
+        foreach (var invokerLoader in _invokerLoaders)
         {
         {
-            var invokers = new ConcurrentDictionary<MessageTypeId, List<IMessageHandlerInvoker>>();
-            var typeSource = CreateTypeSource();
+            var loadedInvokers = invokerLoader.LoadMessageHandlerInvokers(typeSource);
 
 
-            foreach (var invokerLoader in _invokerLoaders)
+            foreach (var invoker in loadedInvokers)
             {
             {
-                var loadedInvokers = invokerLoader.LoadMessageHandlerInvokers(typeSource);
+                if (_handlerFilter != null && !_handlerFilter(invoker.MessageHandlerType))
+                    continue;
 
 
-                foreach (var invoker in loadedInvokers)
-                {
-                    if (_handlerFilter != null && !_handlerFilter(invoker.MessageHandlerType))
-                        continue;
+                if (_messageFilter != null && !_messageFilter(invoker.MessageType))
+                    continue;
 
 
-                    if (_messageFilter != null && !_messageFilter(invoker.MessageType))
-                        continue;
-
-                    var messageTypeInvokers = invokers.GetOrAdd(new MessageTypeId(invoker.MessageType), x => new List<IMessageHandlerInvoker>());
-                    messageTypeInvokers.Add(invoker);
-                }
+                var messageTypeInvokers = invokers.GetOrAdd(new MessageTypeId(invoker.MessageType), x => new List<IMessageHandlerInvoker>());
+                messageTypeInvokers.Add(invoker);
             }
             }
-
-            return invokers;
         }
         }
 
 
-        private void LoadDispatchQueues()
+        return invokers;
+    }
+
+    private void LoadDispatchQueues()
+    {
+        foreach (var invoker in _invokers.Values.SelectMany(x => x).Where(x => !_dispatchQueues.ContainsKey(x.DispatchQueueName)))
         {
         {
-            foreach (var invoker in _invokers.Values.SelectMany(x => x).Where(x => !_dispatchQueues.ContainsKey(x.DispatchQueueName)))
-            {
-                _dispatchQueues.TryAdd(invoker.DispatchQueueName, _dispatchQueueFactory.Create(invoker.DispatchQueueName));
-            }
+            _dispatchQueues.TryAdd(invoker.DispatchQueueName, _dispatchQueueFactory.Create(invoker.DispatchQueueName));
         }
         }
+    }
 
 
-        public IEnumerable<MessageTypeId> GetHandledMessageTypes() => _invokers.Keys;
+    public IEnumerable<MessageTypeId> GetHandledMessageTypes() => _invokers.Keys;
 
 
-        public IEnumerable<IMessageHandlerInvoker> GetMessageHandlerInvokers() => _invokers.SelectMany(x => x.Value);
+    public IEnumerable<IMessageHandlerInvoker> GetMessageHandlerInvokers() => _invokers.SelectMany(x => x.Value);
 
 
-        public void Dispatch(MessageDispatch dispatch)
-        {
-            var invokers = _invokers.GetValueOrDefault(dispatch.Message.TypeId(), _emptyInvokers);
-            Dispatch(dispatch, invokers);
-        }
+    public void Dispatch(MessageDispatch dispatch)
+    {
+        var invokers = _invokers.GetValueOrDefault(dispatch.Message.TypeId(), _emptyInvokers);
+        Dispatch(dispatch, invokers);
+    }
 
 
-        public void Dispatch(MessageDispatch dispatch, Func<Type, bool> handlerFilter)
-        {
-            var invokers = _invokers.GetValueOrDefault(dispatch.Message.TypeId(), _emptyInvokers).Where(x => handlerFilter(x.MessageHandlerType)).ToList();
-            Dispatch(dispatch, invokers);
-        }
+    public void Dispatch(MessageDispatch dispatch, Func<Type, bool> handlerFilter)
+    {
+        var invokers = _invokers.GetValueOrDefault(dispatch.Message.TypeId(), _emptyInvokers).Where(x => handlerFilter(x.MessageHandlerType)).ToList();
+        Dispatch(dispatch, invokers);
+    }
 
 
-        private void Dispatch(MessageDispatch dispatch, List<IMessageHandlerInvoker> invokers)
+    private void Dispatch(MessageDispatch dispatch, List<IMessageHandlerInvoker> invokers)
+    {
+        switch (_status)
         {
         {
-            switch (_status)
-            {
-                case Status.Stopped:
-                    throw new InvalidOperationException("MessageDispatcher is stopped");
+            case Status.Stopped:
+                throw new InvalidOperationException("MessageDispatcher is stopped");
 
 
-                case Status.Stopping:
-                    if (dispatch.IsLocal)
-                        break;
+            case Status.Stopping:
+                if (dispatch.IsLocal)
+                    break;
 
 
-                    throw new InvalidOperationException("MessageDispatcher is stopping");
-            }
+                throw new InvalidOperationException("MessageDispatcher is stopping");
+        }
 
 
-            if (invokers.Count == 0)
-            {
-                dispatch.SetIgnored();
-                return;
-            }
+        if (invokers.Count == 0)
+        {
+            dispatch.SetIgnored();
+            return;
+        }
 
 
-            dispatch.SetHandlerCount(invokers.Count);
+        dispatch.SetHandlerCount(invokers.Count);
 
 
-            foreach (var invoker in invokers)
-            {
-                if (invoker.ShouldHandle(dispatch.Message))
-                    Dispatch(dispatch, invoker);
-            }
+        foreach (var invoker in invokers)
+        {
+            if (invoker.ShouldHandle(dispatch.Message))
+                Dispatch(dispatch, invoker);
         }
         }
+    }
 
 
-        public void Stop()
-        {
-            if (_status != Status.Loaded && _status != Status.Started)
-                return;
+    public void Stop()
+    {
+        if (_status != Status.Loaded && _status != Status.Started)
+            return;
 
 
-            _status = Status.Stopping;
+        _status = Status.Stopping;
 
 
-            OnStopping();
+        OnStopping();
 
 
-            WaitUntilAllMessagesAreProcessed();
+        WaitUntilAllMessagesAreProcessed();
 
 
-            _status = Status.Stopped;
+        _status = Status.Stopped;
 
 
-            var stopTasks = _dispatchQueues.Values.Select(dispatchQueue => Task.Run(dispatchQueue.Stop)).ToArray();
-            Task.WaitAll(stopTasks);
-        }
+        var stopTasks = _dispatchQueues.Values.Select(dispatchQueue => Task.Run(dispatchQueue.Stop)).ToArray();
+        Task.WaitAll(stopTasks);
+    }
 
 
-        private void OnStopping()
+    private void OnStopping()
+    {
+        try
         {
         {
-            try
-            {
-                Stopping?.Invoke();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Stopping event handler error");
-            }
+            Stopping?.Invoke();
         }
         }
-
-        public void Start()
+        catch (Exception ex)
         {
         {
-            if (_status == Status.Started)
-                return;
+            _logger.LogError(ex, "Stopping event handler error");
+        }
+    }
 
 
-            if (_status != Status.Loaded)
-                throw new InvalidOperationException("MessageDispatcher should be loaded before start");
+    public void Start()
+    {
+        if (_status == Status.Started)
+            return;
 
 
-            OnStarting();
+        if (_status != Status.Loaded)
+            throw new InvalidOperationException("MessageDispatcher should be loaded before start");
 
 
-            lock (_lock)
-            {
-                _status = Status.Started;
+        OnStarting();
 
 
-                foreach (var dispatchQueue in _dispatchQueues.Values)
-                {
-                    dispatchQueue.Start();
-                }
+        lock (_lock)
+        {
+            _status = Status.Started;
+
+            foreach (var dispatchQueue in _dispatchQueues.Values)
+            {
+                dispatchQueue.Start();
             }
             }
         }
         }
+    }
 
 
-        private void OnStarting()
+    private void OnStarting()
+    {
+        try
         {
         {
-            try
-            {
-                Starting?.Invoke();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Starting event handler error");
-            }
+            Starting?.Invoke();
+        }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Starting event handler error");
         }
         }
+    }
 
 
-        private void WaitUntilAllMessagesAreProcessed()
+    private void WaitUntilAllMessagesAreProcessed()
+    {
+        bool continueWait;
+        do
         {
         {
-            bool continueWait;
-            do
-            {
-                continueWait = false;
+            continueWait = false;
 
 
-                foreach (var dispatchQueue in _dispatchQueues.Values)
-                {
-                    continueWait = dispatchQueue.WaitUntilAllMessagesAreProcessed() || continueWait;
-                }
-            } while (continueWait);
-        }
+            foreach (var dispatchQueue in _dispatchQueues.Values)
+            {
+                continueWait = dispatchQueue.WaitUntilAllMessagesAreProcessed() || continueWait;
+            }
+        } while (continueWait);
+    }
 
 
-        public void AddInvoker(IMessageHandlerInvoker newEventHandlerInvoker)
+    public void AddInvoker(IMessageHandlerInvoker newEventHandlerInvoker)
+    {
+        lock (_lock)
         {
         {
-            lock (_lock)
+            var existingMessageTypeInvokers = _invokers.GetValueOrDefault(newEventHandlerInvoker.MessageTypeId) ?? new List<IMessageHandlerInvoker>();
+            var newMessageTypeInvokers = new List<IMessageHandlerInvoker>(existingMessageTypeInvokers.Count + 1);
+            newMessageTypeInvokers.AddRange(existingMessageTypeInvokers);
+            newMessageTypeInvokers.Add(newEventHandlerInvoker);
+
+            var dispatchQueueName = newEventHandlerInvoker.DispatchQueueName;
+            if (!_dispatchQueues.ContainsKey(dispatchQueueName))
             {
             {
-                var existingMessageTypeInvokers = _invokers.GetValueOrDefault(newEventHandlerInvoker.MessageTypeId) ?? new List<IMessageHandlerInvoker>();
-                var newMessageTypeInvokers = new List<IMessageHandlerInvoker>(existingMessageTypeInvokers.Count + 1);
-                newMessageTypeInvokers.AddRange(existingMessageTypeInvokers);
-                newMessageTypeInvokers.Add(newEventHandlerInvoker);
-
-                var dispatchQueueName = newEventHandlerInvoker.DispatchQueueName;
-                if (!_dispatchQueues.ContainsKey(dispatchQueueName))
-                {
-                    var dispatchQueue = _dispatchQueueFactory.Create(dispatchQueueName);
-                    if (_dispatchQueues.TryAdd(dispatchQueueName, dispatchQueue) && _status == Status.Started)
-                        dispatchQueue.Start();
-                }
-
-                _invokers[newEventHandlerInvoker.MessageTypeId] = newMessageTypeInvokers;
+                var dispatchQueue = _dispatchQueueFactory.Create(dispatchQueueName);
+                if (_dispatchQueues.TryAdd(dispatchQueueName, dispatchQueue) && _status == Status.Started)
+                    dispatchQueue.Start();
             }
             }
 
 
-            MessageHandlerInvokersUpdated?.Invoke();
+            _invokers[newEventHandlerInvoker.MessageTypeId] = newMessageTypeInvokers;
         }
         }
 
 
-        public void RemoveInvoker(IMessageHandlerInvoker eventHandlerInvoker)
-        {
-            lock (_lock)
-            {
-                var messageTypeInvokers = _invokers.GetValueOrDefault(eventHandlerInvoker.MessageTypeId);
-                if (messageTypeInvokers == null)
-                    return;
+        MessageHandlerInvokersUpdated?.Invoke();
+    }
 
 
-                var newMessageTypeInvokers = new List<IMessageHandlerInvoker>(messageTypeInvokers.Where(x => x != eventHandlerInvoker));
-                _invokers[eventHandlerInvoker.MessageTypeId] = newMessageTypeInvokers;
-            }
+    public void RemoveInvoker(IMessageHandlerInvoker eventHandlerInvoker)
+    {
+        lock (_lock)
+        {
+            var messageTypeInvokers = _invokers.GetValueOrDefault(eventHandlerInvoker.MessageTypeId);
+            if (messageTypeInvokers == null)
+                return;
 
 
-            MessageHandlerInvokersUpdated?.Invoke();
+            var newMessageTypeInvokers = new List<IMessageHandlerInvoker>(messageTypeInvokers.Where(x => x != eventHandlerInvoker));
+            _invokers[eventHandlerInvoker.MessageTypeId] = newMessageTypeInvokers;
         }
         }
 
 
-        public int Purge() => _dispatchQueues.Values.Sum(x => x.Purge());
+        MessageHandlerInvokersUpdated?.Invoke();
+    }
+
+    public int Purge() => _dispatchQueues.Values.Sum(x => x.Purge());
 
 
-        public int GetReceiveQueueLength() => _dispatchQueues.Values.Sum(x => x.QueueLength);
+    public int GetReceiveQueueLength() => _dispatchQueues.Values.Sum(x => x.QueueLength);
 
 
-        private TypeSource CreateTypeSource()
-        {
-            var typeSource = new TypeSource();
+    private TypeSource CreateTypeSource()
+    {
+        var typeSource = new TypeSource();
 
 
-            if (_assemblyFilter != null)
-                typeSource.AssemblyFilter = _assemblyFilter;
+        if (_assemblyFilter != null)
+            typeSource.AssemblyFilter = _assemblyFilter;
 
 
-            if (_handlerFilter != null)
-                typeSource.TypeFilter = _handlerFilter;
+        if (_handlerFilter != null)
+            typeSource.TypeFilter = _handlerFilter;
 
 
-            return typeSource;
-        }
+        return typeSource;
+    }
 
 
-        private void Dispatch(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
-        {
-            var dispatchQueue = _dispatchQueues[invoker.DispatchQueueName];
-            dispatchQueue.RunOrEnqueue(dispatch, invoker);
-        }
+    private void Dispatch(MessageDispatch dispatch, IMessageHandlerInvoker invoker)
+    {
+        var dispatchQueue = _dispatchQueues[invoker.DispatchQueueName];
+        dispatchQueue.RunOrEnqueue(dispatch, invoker);
+    }
 
 
-        private enum Status
-        {
-            Stopped,
-            Loaded,
-            Started,
-            Stopping,
-        }
+    private enum Status
+    {
+        Stopped,
+        Loaded,
+        Started,
+        Stopping,
     }
     }
 }
 }

+ 66 - 67
src/Abc.Zebus/Dispatch/MessageHandlerInvoker.cs

@@ -9,91 +9,90 @@ using Abc.Zebus.Util.Extensions;
 using StructureMap;
 using StructureMap;
 using StructureMap.Pipeline;
 using StructureMap.Pipeline;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public abstract class MessageHandlerInvoker : IMessageHandlerInvoker
 {
 {
-    public abstract class MessageHandlerInvoker : IMessageHandlerInvoker
+    private readonly IContainer _container;
+    private readonly MessageHandlerInvokerSubscriber _subscriber;
+    private readonly Instance _instance;
+    private bool? _isSingleton;
+    private IBus? _bus;
+
+    [ThreadStatic]
+    private static MessageContextAwareBus? _dispatchBus;
+
+    protected MessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
     {
     {
-        private readonly IContainer _container;
-        private readonly MessageHandlerInvokerSubscriber _subscriber;
-        private readonly Instance _instance;
-        private bool? _isSingleton;
-        private IBus? _bus;
+        _container = container;
+        _subscriber = subscriber;
+        _instance = CreateConstructorInstance(handlerType);
+
+        MessageHandlerType = handlerType;
+        DispatchQueueName = DispatchQueueNameScanner.GetQueueName(handlerType);
+        MessageType = messageType;
+        MessageTypeId = new MessageTypeId(messageType);
+    }
 
 
-        [ThreadStatic]
-        private static MessageContextAwareBus? _dispatchBus;
+    public Type MessageHandlerType { get; }
+    public Type MessageType { get; }
+    public MessageTypeId MessageTypeId { get; }
+    public string DispatchQueueName { get; }
+    public virtual MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
 
 
-        protected MessageHandlerInvoker(IContainer container, Type handlerType, Type messageType, MessageHandlerInvokerSubscriber subscriber)
-        {
-            _container = container;
-            _subscriber = subscriber;
-            _instance = CreateConstructorInstance(handlerType);
-
-            MessageHandlerType = handlerType;
-            DispatchQueueName = DispatchQueueNameScanner.GetQueueName(handlerType);
-            MessageType = messageType;
-            MessageTypeId = new MessageTypeId(messageType);
-        }
+    public IEnumerable<Subscription> GetStartupSubscriptions()
+        => _subscriber.GetStartupSubscriptions(MessageType, MessageTypeId, _container);
 
 
-        public Type MessageHandlerType { get; }
-        public Type MessageType { get; }
-        public MessageTypeId MessageTypeId { get; }
-        public string DispatchQueueName { get; }
-        public virtual MessageHandlerInvokerMode Mode => MessageHandlerInvokerMode.Synchronous;
+    public abstract void InvokeMessageHandler(IMessageHandlerInvocation invocation);
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions()
-            => _subscriber.GetStartupSubscriptions(MessageType, MessageTypeId, _container);
+    public virtual Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
+        => throw new NotSupportedException("InvokeMessageHandlerAsync is not supported in Synchronous mode");
 
 
-        public abstract void InvokeMessageHandler(IMessageHandlerInvocation invocation);
+    public virtual bool ShouldHandle(IMessage message) => true;
 
 
-        public virtual Task InvokeMessageHandlerAsync(IMessageHandlerInvocation invocation)
-            => throw new NotSupportedException("InvokeMessageHandlerAsync is not supported in Synchronous mode");
+    public virtual bool CanMergeWith(IMessageHandlerInvoker other) => false;
 
 
-        public virtual bool ShouldHandle(IMessage message) => true;
+    protected internal object CreateHandler(MessageContext messageContext)
+    {
+        if (IsHandlerSingleton())
+            return _container.GetInstance(MessageHandlerType);
 
 
-        public virtual bool CanMergeWith(IMessageHandlerInvoker other) => false;
+        _bus ??= _container.GetInstance<IBus>();
+        if (_bus == null)
+            return _container.GetInstance(MessageHandlerType);
 
 
-        protected internal object CreateHandler(MessageContext messageContext)
+        try
         {
         {
-            if (IsHandlerSingleton())
-                return _container.GetInstance(MessageHandlerType);
-
-            _bus ??= _container.GetInstance<IBus>();
-            if (_bus == null)
-                return _container.GetInstance(MessageHandlerType);
-
-            try
-            {
-                _dispatchBus = new MessageContextAwareBus(_bus, messageContext);
-                return _container.GetInstance(MessageHandlerType, _instance);
-            }
-            finally
-            {
-                _dispatchBus = null;
-            }
+            _dispatchBus = new MessageContextAwareBus(_bus, messageContext);
+            return _container.GetInstance(MessageHandlerType, _instance);
         }
         }
-
-        private bool IsHandlerSingleton()
+        finally
         {
         {
-            if (_isSingleton == null)
-            {
-                var model = _container.Model?.For(MessageHandlerType);
-                _isSingleton = model != null && model.Lifecycle == Lifecycles.Singleton;
-            }
-            return _isSingleton.Value;
+            _dispatchBus = null;
         }
         }
+    }
 
 
-        private static Instance CreateConstructorInstance(Type messageHandlerType)
+    private bool IsHandlerSingleton()
+    {
+        if (_isSingleton == null)
         {
         {
-            var inst = new ConstructorInstance(messageHandlerType);
-            inst.Dependencies.Add<IBus>(new LambdaInstance<IBus>("Dispatch IBus", () => _dispatchBus!));
-            inst.Dependencies.Add<MessageContext>(new LambdaInstance<MessageContext>("Dispatch MessageContext", () => _dispatchBus!.MessageContext));
-            return inst;
+            var model = _container.Model?.For(MessageHandlerType);
+            _isSingleton = model != null && model.Lifecycle == Lifecycles.Singleton;
         }
         }
+        return _isSingleton.Value;
+    }
 
 
-        internal static void ThrowIfAsyncVoid(Type handlerType, MethodInfo handleMethod)
-        {
-            if (handleMethod.ReturnType == typeof(void) && handleMethod.GetAttribute<AsyncStateMachineAttribute>(true) != null)
-                throw new InvalidProgramException($"The message handler {handlerType} has an async void Handle method. If you think there are valid use cases for this, please discuss it with the dev team");
-        }
+    private static Instance CreateConstructorInstance(Type messageHandlerType)
+    {
+        var inst = new ConstructorInstance(messageHandlerType);
+        inst.Dependencies.Add<IBus>(new LambdaInstance<IBus>("Dispatch IBus", () => _dispatchBus!));
+        inst.Dependencies.Add<MessageContext>(new LambdaInstance<MessageContext>("Dispatch MessageContext", () => _dispatchBus!.MessageContext));
+        return inst;
+    }
+
+    internal static void ThrowIfAsyncVoid(Type handlerType, MethodInfo handleMethod)
+    {
+        if (handleMethod.ReturnType == typeof(void) && handleMethod.GetAttribute<AsyncStateMachineAttribute>(true) != null)
+            throw new InvalidProgramException($"The message handler {handlerType} has an async void Handle method. If you think there are valid use cases for this, please discuss it with the dev team");
     }
     }
 }
 }

+ 6 - 7
src/Abc.Zebus/Dispatch/MessageHandlerInvokerMode.cs

@@ -1,8 +1,7 @@
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public enum MessageHandlerInvokerMode
 {
 {
-    public enum MessageHandlerInvokerMode
-    {
-        Synchronous,
-        Asynchronous,
-    }
-}
+    Synchronous,
+    Asynchronous,
+}

+ 60 - 61
src/Abc.Zebus/Dispatch/MessageHandlerInvokerSubscriber.cs

@@ -4,82 +4,81 @@ using System.Linq;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
 using StructureMap;
 using StructureMap;
 
 
-namespace Abc.Zebus.Dispatch
+namespace Abc.Zebus.Dispatch;
+
+public class MessageHandlerInvokerSubscriber
 {
 {
-    public class MessageHandlerInvokerSubscriber
-    {
-        private readonly SubscriptionMode? _subscriptionMode;
-        private readonly Type? _startupSubscriberType;
+    private readonly SubscriptionMode? _subscriptionMode;
+    private readonly Type? _startupSubscriberType;
 
 
-        public MessageHandlerInvokerSubscriber(SubscriptionMode? subscriptionMode, Type? startupSubscriberType)
-        {
-            _subscriptionMode = subscriptionMode;
-            _startupSubscriberType = startupSubscriberType;
-        }
+    public MessageHandlerInvokerSubscriber(SubscriptionMode? subscriptionMode, Type? startupSubscriberType)
+    {
+        _subscriptionMode = subscriptionMode;
+        _startupSubscriberType = startupSubscriberType;
+    }
 
 
-        public static MessageHandlerInvokerSubscriber FromAttributes(Type messageHandlerType)
-        {
-            var subscriptionModeAttribute = (SubscriptionModeAttribute?)Attribute.GetCustomAttribute(messageHandlerType, typeof(SubscriptionModeAttribute));
-            if (subscriptionModeAttribute != null)
-                return new MessageHandlerInvokerSubscriber(subscriptionModeAttribute.SubscriptionMode, subscriptionModeAttribute.StartupSubscriberType);
+    public static MessageHandlerInvokerSubscriber FromAttributes(Type messageHandlerType)
+    {
+        var subscriptionModeAttribute = (SubscriptionModeAttribute?)Attribute.GetCustomAttribute(messageHandlerType, typeof(SubscriptionModeAttribute));
+        if (subscriptionModeAttribute != null)
+            return new MessageHandlerInvokerSubscriber(subscriptionModeAttribute.SubscriptionMode, subscriptionModeAttribute.StartupSubscriberType);
 
 
-            if (Attribute.IsDefined(messageHandlerType, typeof(NoScanAttribute)))
-                return new MessageHandlerInvokerSubscriber(SubscriptionMode.Manual, startupSubscriberType: null);
+        if (Attribute.IsDefined(messageHandlerType, typeof(NoScanAttribute)))
+            return new MessageHandlerInvokerSubscriber(SubscriptionMode.Manual, startupSubscriberType: null);
 
 
-            return new MessageHandlerInvokerSubscriber(subscriptionMode: null, startupSubscriberType: null);
-        }
+        return new MessageHandlerInvokerSubscriber(subscriptionMode: null, startupSubscriberType: null);
+    }
 
 
-        public IEnumerable<Subscription> GetStartupSubscriptions(Type messageType, MessageTypeId messageTypeId, IContainer container)
+    public IEnumerable<Subscription> GetStartupSubscriptions(Type messageType, MessageTypeId messageTypeId, IContainer container)
+    {
+        if (_startupSubscriberType != null)
         {
         {
-            if (_startupSubscriberType != null)
-            {
-                var startupSubscriber = (IStartupSubscriber)container.GetInstance(_startupSubscriberType);
-                return GetSubscriptionsFromSubscriber(startupSubscriber, messageTypeId, messageType);
-            }
+            var startupSubscriber = (IStartupSubscriber)container.GetInstance(_startupSubscriberType);
+            return GetSubscriptionsFromSubscriber(startupSubscriber, messageTypeId, messageType);
+        }
 
 
-            var subscriptionMode = _subscriptionMode ?? DefaultSubscriptionMode(messageType);
+        var subscriptionMode = _subscriptionMode ?? DefaultSubscriptionMode(messageType);
 
 
-            return GetSubscriptionsFromMode(subscriptionMode, messageTypeId);
-        }
+        return GetSubscriptionsFromMode(subscriptionMode, messageTypeId);
+    }
 
 
-        /// <summary>
-        /// Gets the default subscription mode for the specified message handler and message type.
-        /// </summary>
-        /// <remarks>
-        /// Note that the startup subscriptions can be overriden if the message handler uses a <see cref="IStartupSubscriber"/>.
-        /// </remarks>
-        public static SubscriptionMode GetDefaultSubscriptionMode(Type messageHandlerType, Type messageType)
-        {
-            var subscriptionModeAttribute = (SubscriptionModeAttribute?)Attribute.GetCustomAttribute(messageHandlerType, typeof(SubscriptionModeAttribute));
-            if (subscriptionModeAttribute != null && subscriptionModeAttribute.SubscriptionMode != null)
-                return subscriptionModeAttribute.SubscriptionMode.Value;
+    /// <summary>
+    /// Gets the default subscription mode for the specified message handler and message type.
+    /// </summary>
+    /// <remarks>
+    /// Note that the startup subscriptions can be overriden if the message handler uses a <see cref="IStartupSubscriber"/>.
+    /// </remarks>
+    public static SubscriptionMode GetDefaultSubscriptionMode(Type messageHandlerType, Type messageType)
+    {
+        var subscriptionModeAttribute = (SubscriptionModeAttribute?)Attribute.GetCustomAttribute(messageHandlerType, typeof(SubscriptionModeAttribute));
+        if (subscriptionModeAttribute != null && subscriptionModeAttribute.SubscriptionMode != null)
+            return subscriptionModeAttribute.SubscriptionMode.Value;
 
 
-            return DefaultSubscriptionMode(messageType);
-        }
+        return DefaultSubscriptionMode(messageType);
+    }
 
 
-        private static IEnumerable<Subscription> GetSubscriptionsFromSubscriber(IStartupSubscriber startupSubscriber, MessageTypeId messageTypeId, Type messageType)
-        {
-            return startupSubscriber.GetStartupSubscriptionBindingKeys(messageType)
-                                    .Select(x => new Subscription(messageTypeId, x))
-                                    .ToArray();
-        }
+    private static IEnumerable<Subscription> GetSubscriptionsFromSubscriber(IStartupSubscriber startupSubscriber, MessageTypeId messageTypeId, Type messageType)
+    {
+        return startupSubscriber.GetStartupSubscriptionBindingKeys(messageType)
+                                .Select(x => new Subscription(messageTypeId, x))
+                                .ToArray();
+    }
 
 
-        private static SubscriptionMode DefaultSubscriptionMode(Type messageType)
-        {
-            if (Attribute.GetCustomAttribute(messageType, typeof(Routable), true) is Routable { AutoSubscribe: false })
-                return SubscriptionMode.Manual;
+    private static SubscriptionMode DefaultSubscriptionMode(Type messageType)
+    {
+        if (Attribute.GetCustomAttribute(messageType, typeof(Routable), true) is Routable { AutoSubscribe: false })
+            return SubscriptionMode.Manual;
 
 
-            return SubscriptionMode.Auto;
-        }
+        return SubscriptionMode.Auto;
+    }
 
 
-        private IEnumerable<Subscription> GetSubscriptionsFromMode(SubscriptionMode subscriptionMode, MessageTypeId messageTypeId)
+    private IEnumerable<Subscription> GetSubscriptionsFromMode(SubscriptionMode subscriptionMode, MessageTypeId messageTypeId)
+    {
+        return subscriptionMode switch
         {
         {
-            return subscriptionMode switch
-            {
-                SubscriptionMode.Auto   => new[] { new Subscription(messageTypeId) },
-                SubscriptionMode.Manual => Array.Empty<Subscription>(),
-                _                       => throw new NotSupportedException($"Unsupported subscription mode: {subscriptionMode}"),
-            };
-        }
+            SubscriptionMode.Auto   => new[] { new Subscription(messageTypeId) },
+            SubscriptionMode.Manual => Array.Empty<Subscription>(),
+            _                       => throw new NotSupportedException($"Unsupported subscription mode: {subscriptionMode}"),
+        };
     }
     }
 }
 }

+ 13 - 14
src/Abc.Zebus/Dispatch/Pipes/AfterInvokeArgs.cs

@@ -1,20 +1,19 @@
 using System;
 using System;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public readonly struct AfterInvokeArgs
 {
 {
-    public readonly struct AfterInvokeArgs
-    {
-        public readonly PipeInvocation Invocation;
-        public readonly object? State;
-        public readonly bool IsFaulted;
-        public readonly Exception? Exception;
+    public readonly PipeInvocation Invocation;
+    public readonly object? State;
+    public readonly bool IsFaulted;
+    public readonly Exception? Exception;
 
 
-        public AfterInvokeArgs(PipeInvocation invocation, object? state, bool isFaulted, Exception? exception)
-        {
-            Invocation = invocation;
-            State = state;
-            IsFaulted = isFaulted;
-            Exception = exception;
-        }
+    public AfterInvokeArgs(PipeInvocation invocation, object? state, bool isFaulted, Exception? exception)
+    {
+        Invocation = invocation;
+        State = state;
+        IsFaulted = isFaulted;
+        Exception = exception;
     }
     }
 }
 }

+ 13 - 14
src/Abc.Zebus/Dispatch/Pipes/AttributePipeSource.cs

@@ -3,21 +3,20 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using StructureMap;
 using StructureMap;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public class AttributePipeSource : IPipeSource
 {
 {
-    public class AttributePipeSource : IPipeSource
-    {
-        private readonly IContainer _container;
+    private readonly IContainer _container;
 
 
-        public AttributePipeSource(IContainer container)
-        {
-            _container = container;
-        }
+    public AttributePipeSource(IContainer container)
+    {
+        _container = container;
+    }
 
 
-        public IEnumerable<IPipe> GetPipes(Type messageHandlerType)
-        {
-            var attributes = (PipeAttribute[])messageHandlerType.GetCustomAttributes(typeof(PipeAttribute), true);
-            return attributes.Select(x => (IPipe)_container.GetInstance(x.PipeType));
-        }
+    public IEnumerable<IPipe> GetPipes(Type messageHandlerType)
+    {
+        var attributes = (PipeAttribute[])messageHandlerType.GetCustomAttributes(typeof(PipeAttribute), true);
+        return attributes.Select(x => (IPipe)_container.GetInstance(x.PipeType));
     }
     }
-}
+}

+ 22 - 23
src/Abc.Zebus/Dispatch/Pipes/BeforeInvokeArgs.cs

@@ -1,31 +1,30 @@
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public readonly struct BeforeInvokeArgs
 {
 {
-    public readonly struct BeforeInvokeArgs
-    {
-        private readonly StateRef _stateRef;
+    private readonly StateRef _stateRef;
 
 
-        public BeforeInvokeArgs(PipeInvocation invocation)
-            : this(invocation, new StateRef())
-        {
-        }
+    public BeforeInvokeArgs(PipeInvocation invocation)
+        : this(invocation, new StateRef())
+    {
+    }
 
 
-        public BeforeInvokeArgs(PipeInvocation invocation, StateRef stateRef)
-        {
-            _stateRef = stateRef;
-            Invocation = invocation;
-        }
+    public BeforeInvokeArgs(PipeInvocation invocation, StateRef stateRef)
+    {
+        _stateRef = stateRef;
+        Invocation = invocation;
+    }
 
 
-        public readonly PipeInvocation Invocation;
+    public readonly PipeInvocation Invocation;
 
 
-        public object? State
-        {
-            get => _stateRef.Value;
-            set => _stateRef.Value = value;
-        }
+    public object? State
+    {
+        get => _stateRef.Value;
+        set => _stateRef.Value = value;
+    }
 
 
-        public class StateRef
-        {
-            public object? Value;
-        }
+    public class StateRef
+    {
+        public object? Value;
     }
     }
 }
 }

+ 9 - 10
src/Abc.Zebus/Dispatch/Pipes/IPipe.cs

@@ -1,12 +1,11 @@
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public interface IPipe
 {
 {
-    public interface IPipe
-    {
-        string Name { get; }
-        int Priority { get; }
-        bool IsAutoEnabled { get; }
+    string Name { get; }
+    int Priority { get; }
+    bool IsAutoEnabled { get; }
 
 
-        void BeforeInvoke(BeforeInvokeArgs args);
-        void AfterInvoke(AfterInvokeArgs args);
-    }
-}
+    void BeforeInvoke(BeforeInvokeArgs args);
+    void AfterInvoke(AfterInvokeArgs args);
+}

+ 7 - 8
src/Abc.Zebus/Dispatch/Pipes/IPipeManager.cs

@@ -1,12 +1,11 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public interface IPipeManager
 {
 {
-    public interface IPipeManager
-    {
-        void EnablePipe(string pipeName);
-        void DisablePipe(string pipeName);
+    void EnablePipe(string pipeName);
+    void DisablePipe(string pipeName);
 
 
-        PipeInvocation BuildPipeInvocation(IMessageHandlerInvoker messageHandlerInvoker, List<IMessage> messages, MessageContext messageContext);
-    }
-}
+    PipeInvocation BuildPipeInvocation(IMessageHandlerInvoker messageHandlerInvoker, List<IMessage> messages, MessageContext messageContext);
+}

+ 5 - 6
src/Abc.Zebus/Dispatch/Pipes/IPipeSource.cs

@@ -1,10 +1,9 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public interface IPipeSource
 {
 {
-    public interface IPipeSource
-    {
-        IEnumerable<IPipe> GetPipes(Type messageHandlerType);
-    }
-}
+    IEnumerable<IPipe> GetPipes(Type messageHandlerType);
+}

+ 8 - 9
src/Abc.Zebus/Dispatch/Pipes/PipeAttribute.cs

@@ -1,16 +1,15 @@
 using System;
 using System;
 using JetBrains.Annotations;
 using JetBrains.Annotations;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+[AttributeUsage(AttributeTargets.Class), UsedImplicitly]
+public class PipeAttribute : Attribute
 {
 {
-    [AttributeUsage(AttributeTargets.Class), UsedImplicitly]
-    public class PipeAttribute : Attribute
+    public PipeAttribute(Type pipeType)
     {
     {
-        public PipeAttribute(Type pipeType)
-        {
-            PipeType = pipeType;
-        }
-
-        public Type PipeType { get; }
+        PipeType = pipeType;
     }
     }
+
+    public Type PipeType { get; }
 }
 }

+ 85 - 86
src/Abc.Zebus/Dispatch/Pipes/PipeInvocation.cs

@@ -4,122 +4,121 @@ using System.Threading.Tasks;
 using Abc.Zebus.Core;
 using Abc.Zebus.Core;
 using Abc.Zebus.Util.Extensions;
 using Abc.Zebus.Util.Extensions;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+public class PipeInvocation : IMessageHandlerInvocation
 {
 {
-    public class PipeInvocation : IMessageHandlerInvocation
+    private static readonly BusMessageLogger _messageLogger = new("Abc.Zebus.Dispatch");
+
+    private readonly List<Action<object>> _handlerMutations = new();
+    private readonly IMessageHandlerInvoker _invoker;
+    private readonly IList<IMessage> _messages;
+    private readonly MessageContext _messageContext;
+    private readonly IList<IPipe> _pipes;
+
+    public PipeInvocation(IMessageHandlerInvoker invoker, List<IMessage> messages, MessageContext messageContext, IEnumerable<IPipe> pipes)
     {
     {
-        private static readonly BusMessageLogger _messageLogger = new BusMessageLogger("Abc.Zebus.Dispatch");
+        _invoker = invoker;
+        _messages = messages;
+        _messageContext = messageContext;
+        _pipes = pipes.AsList();
+    }
 
 
-        private readonly List<Action<object>> _handlerMutations = new List<Action<object>>();
-        private readonly IMessageHandlerInvoker _invoker;
-        private readonly IList<IMessage> _messages;
-        private readonly MessageContext _messageContext;
-        private readonly IList<IPipe> _pipes;
+    internal IList<IPipe> Pipes => _pipes;
 
 
-        public PipeInvocation(IMessageHandlerInvoker invoker, List<IMessage> messages, MessageContext messageContext, IEnumerable<IPipe> pipes)
-        {
-            _invoker = invoker;
-            _messages = messages;
-            _messageContext = messageContext;
-            _pipes = pipes.AsList();
-        }
+    public IMessageHandlerInvoker Invoker => _invoker;
 
 
-        internal IList<IPipe> Pipes => _pipes;
+    public IList<IMessage> Messages => _messages;
 
 
-        public IMessageHandlerInvoker Invoker => _invoker;
+    public MessageContext Context => _messageContext;
 
 
-        public IList<IMessage> Messages => _messages;
+    public void AddHandlerMutation(Action<object> action)
+    {
+        _handlerMutations.Add(action);
+    }
 
 
-        public MessageContext Context => _messageContext;
+    protected internal virtual void Run()
+    {
+        var pipeStates = BeforeInvoke();
 
 
-        public void AddHandlerMutation(Action<object> action)
+        try
         {
         {
-            _handlerMutations.Add(action);
+            _invoker.InvokeMessageHandler(this);
         }
         }
-
-        protected internal virtual void Run()
+        catch (Exception exception)
         {
         {
-            var pipeStates = BeforeInvoke();
-
-            try
-            {
-                _invoker.InvokeMessageHandler(this);
-            }
-            catch (Exception exception)
-            {
-                AfterInvoke(pipeStates, true, exception);
-                throw;
-            }
-
-            AfterInvoke(pipeStates, false, null);
+            AfterInvoke(pipeStates, true, exception);
+            throw;
         }
         }
 
 
-        private object?[] BeforeInvoke()
+        AfterInvoke(pipeStates, false, null);
+    }
+
+    private object?[] BeforeInvoke()
+    {
+        if (_pipes.Count == 0)
+            return Array.Empty<object?>();
+
+        var stateRef = new BeforeInvokeArgs.StateRef();
+        var pipeStates = new object?[_pipes.Count];
+        for (var pipeIndex = 0; pipeIndex < _pipes.Count; ++pipeIndex)
         {
         {
-            if (_pipes.Count == 0)
-                return Array.Empty<object?>();
-
-            var stateRef = new BeforeInvokeArgs.StateRef();
-            var pipeStates = new object?[_pipes.Count];
-            for (var pipeIndex = 0; pipeIndex < _pipes.Count; ++pipeIndex)
-            {
-                var beforeInvokeArgs = new BeforeInvokeArgs(this, stateRef);
-                _pipes[pipeIndex].BeforeInvoke(beforeInvokeArgs);
-                pipeStates[pipeIndex] = beforeInvokeArgs.State;
-            }
-
-            return pipeStates;
+            var beforeInvokeArgs = new BeforeInvokeArgs(this, stateRef);
+            _pipes[pipeIndex].BeforeInvoke(beforeInvokeArgs);
+            pipeStates[pipeIndex] = beforeInvokeArgs.State;
         }
         }
 
 
-        private void AfterInvoke(object?[] pipeStates, bool isFaulted, Exception? exception)
+        return pipeStates;
+    }
+
+    private void AfterInvoke(object?[] pipeStates, bool isFaulted, Exception? exception)
+    {
+        for (var pipeIndex = _pipes.Count - 1; pipeIndex >= 0; --pipeIndex)
         {
         {
-            for (var pipeIndex = _pipes.Count - 1; pipeIndex >= 0; --pipeIndex)
-            {
-                var afterInvokeArgs = new AfterInvokeArgs(this, pipeStates[pipeIndex], isFaulted, exception);
-                _pipes[pipeIndex].AfterInvoke(afterInvokeArgs);
-            }
+            var afterInvokeArgs = new AfterInvokeArgs(this, pipeStates[pipeIndex], isFaulted, exception);
+            _pipes[pipeIndex].AfterInvoke(afterInvokeArgs);
         }
         }
+    }
 
 
-        protected internal virtual Task RunAsync()
-        {
-            var pipeStates = BeforeInvoke();
+    protected internal virtual Task RunAsync()
+    {
+        var pipeStates = BeforeInvoke();
 
 
-            var runTask = _invoker.InvokeMessageHandlerAsync(this);
+        var runTask = _invoker.InvokeMessageHandlerAsync(this);
 
 
-            if (runTask.Status == TaskStatus.Created)
-            {
-                var exception = new InvalidProgramException($"{Invoker.MessageHandlerType.Name}.Handle({Invoker.MessageType.Name}) did not start the returned task");
-                runTask = Task.FromException(exception);
-            }
+        if (runTask.Status == TaskStatus.Created)
+        {
+            var exception = new InvalidProgramException($"{Invoker.MessageHandlerType.Name}.Handle({Invoker.MessageType.Name}) did not start the returned task");
+            runTask = Task.FromException(exception);
+        }
 
 
-            runTask.ContinueWith(task => AfterInvoke(pipeStates, task.IsFaulted || task.IsCanceled, task.Exception), TaskContinuationOptions.ExecuteSynchronously);
+        runTask.ContinueWith(task => AfterInvoke(pipeStates, task.IsFaulted || task.IsCanceled, task.Exception), TaskContinuationOptions.ExecuteSynchronously);
 
 
-            return runTask;
-        }
+        return runTask;
+    }
 
 
-        IDisposable IMessageHandlerInvocation.SetupForInvocation()
-        {
-            _messageLogger.LogHandleMessage(_messages, _invoker.DispatchQueueName, _messageContext.MessageId);
+    IDisposable IMessageHandlerInvocation.SetupForInvocation()
+    {
+        _messageLogger.LogHandleMessage(_messages, _invoker.DispatchQueueName, _messageContext.MessageId);
 
 
-            return MessageContext.SetCurrent(_messageContext);
-        }
+        return MessageContext.SetCurrent(_messageContext);
+    }
 
 
-        IDisposable IMessageHandlerInvocation.SetupForInvocation(object messageHandler)
-        {
-            _messageLogger.LogHandleMessage(_messages, _invoker.DispatchQueueName, _messageContext.MessageId);
+    IDisposable IMessageHandlerInvocation.SetupForInvocation(object messageHandler)
+    {
+        _messageLogger.LogHandleMessage(_messages, _invoker.DispatchQueueName, _messageContext.MessageId);
 
 
-            ApplyMutations(messageHandler);
+        ApplyMutations(messageHandler);
 
 
-            return MessageContext.SetCurrent(_messageContext);
-        }
+        return MessageContext.SetCurrent(_messageContext);
+    }
 
 
-        private void ApplyMutations(object messageHandler)
-        {
-            if (messageHandler is IMessageContextAware messageContextAwareHandler)
-                messageContextAwareHandler.Context = Context;
+    private void ApplyMutations(object messageHandler)
+    {
+        if (messageHandler is IMessageContextAware messageContextAwareHandler)
+            messageContextAwareHandler.Context = Context;
 
 
-            foreach (var messageHandlerMutation in _handlerMutations)
-                messageHandlerMutation(messageHandler);
-        }
+        foreach (var messageHandlerMutation in _handlerMutations)
+            messageHandlerMutation(messageHandler);
     }
     }
 }
 }

+ 58 - 59
src/Abc.Zebus/Dispatch/Pipes/PipeManager.cs

@@ -5,84 +5,83 @@ using System.Linq;
 using Abc.Zebus.Util.Collections;
 using Abc.Zebus.Util.Collections;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Abc.Zebus.Dispatch.Pipes
+namespace Abc.Zebus.Dispatch.Pipes;
+
+internal class PipeManager : IPipeManager
 {
 {
-    internal class PipeManager : IPipeManager
-    {
-        private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(PipeManager));
+    private static readonly ILogger _logger = ZebusLogManager.GetLogger(typeof(PipeManager));
 
 
-        private readonly ConcurrentDictionary<Type, PipeList> _pipesByMessageType = new ConcurrentDictionary<Type, PipeList>();
-        private readonly ConcurrentSet<string> _enabledPipeNames = new ConcurrentSet<string>();
-        private readonly ConcurrentSet<string> _disabledPipeNames = new ConcurrentSet<string>();
-        private readonly Func<Type, PipeList> _createPipeList;
-        private readonly IPipeSource[] _pipeSources;
+    private readonly ConcurrentDictionary<Type, PipeList> _pipesByMessageType = new();
+    private readonly ConcurrentSet<string> _enabledPipeNames = new();
+    private readonly ConcurrentSet<string> _disabledPipeNames = new();
+    private readonly Func<Type, PipeList> _createPipeList;
+    private readonly IPipeSource[] _pipeSources;
 
 
-        public PipeManager(IPipeSource[] pipeSources)
-        {
-            _pipeSources = pipeSources;
-            _createPipeList = CreatePipeList;
-        }
+    public PipeManager(IPipeSource[] pipeSources)
+    {
+        _pipeSources = pipeSources;
+        _createPipeList = CreatePipeList;
+    }
 
 
-        public void EnablePipe(string pipeName)
-        {
-            _logger.LogInformation($"Enabling pipe [{pipeName}]");
+    public void EnablePipe(string pipeName)
+    {
+        _logger.LogInformation($"Enabling pipe [{pipeName}]");
 
 
-            _enabledPipeNames.Add(pipeName);
-            _disabledPipeNames.Remove(pipeName);
+        _enabledPipeNames.Add(pipeName);
+        _disabledPipeNames.Remove(pipeName);
 
 
-            foreach (var pipeListEntry in _pipesByMessageType.Values)
-                pipeListEntry.ReloadEnabledPipes();
-        }
+        foreach (var pipeListEntry in _pipesByMessageType.Values)
+            pipeListEntry.ReloadEnabledPipes();
+    }
 
 
-        public void DisablePipe(string pipeName)
-        {
-            _logger.LogInformation($"Disabling pipe [{pipeName}]");
+    public void DisablePipe(string pipeName)
+    {
+        _logger.LogInformation($"Disabling pipe [{pipeName}]");
 
 
-            _enabledPipeNames.Remove(pipeName);
-            _disabledPipeNames.Add(pipeName);
+        _enabledPipeNames.Remove(pipeName);
+        _disabledPipeNames.Add(pipeName);
 
 
-            foreach (var pipeListEntry in _pipesByMessageType.Values)
-                pipeListEntry.ReloadEnabledPipes();
-        }
+        foreach (var pipeListEntry in _pipesByMessageType.Values)
+            pipeListEntry.ReloadEnabledPipes();
+    }
 
 
-        public PipeInvocation BuildPipeInvocation(IMessageHandlerInvoker messageHandlerInvoker, List<IMessage> messages, MessageContext messageContext)
-        {
-            var pipes = GetEnabledPipes(messageHandlerInvoker.MessageHandlerType);
-            return new PipeInvocation(messageHandlerInvoker, messages, messageContext, pipes);
-        }
+    public PipeInvocation BuildPipeInvocation(IMessageHandlerInvoker messageHandlerInvoker, List<IMessage> messages, MessageContext messageContext)
+    {
+        var pipes = GetEnabledPipes(messageHandlerInvoker.MessageHandlerType);
+        return new PipeInvocation(messageHandlerInvoker, messages, messageContext, pipes);
+    }
 
 
-        public IEnumerable<IPipe> GetEnabledPipes(Type messageHandlerType)
-            => GetPipeList(messageHandlerType).EnabledPipes;
+    public IEnumerable<IPipe> GetEnabledPipes(Type messageHandlerType)
+        => GetPipeList(messageHandlerType).EnabledPipes;
 
 
-        private PipeList GetPipeList(Type messageHandlerType)
-            => _pipesByMessageType.GetOrAdd(messageHandlerType, _createPipeList);
+    private PipeList GetPipeList(Type messageHandlerType)
+        => _pipesByMessageType.GetOrAdd(messageHandlerType, _createPipeList);
 
 
-        private PipeList CreatePipeList(Type handlerType)
-            => new PipeList(this, _pipeSources.SelectMany(x => x.GetPipes(handlerType)));
+    private PipeList CreatePipeList(Type handlerType)
+        => new(this, _pipeSources.SelectMany(x => x.GetPipes(handlerType)));
 
 
-        private bool IsPipeEnabled(IPipe pipe)
-            => !_disabledPipeNames.Contains(pipe.Name)
-               && (pipe.IsAutoEnabled || _enabledPipeNames.Contains(pipe.Name));
+    private bool IsPipeEnabled(IPipe pipe)
+        => !_disabledPipeNames.Contains(pipe.Name)
+           && (pipe.IsAutoEnabled || _enabledPipeNames.Contains(pipe.Name));
 
 
-        private class PipeList
-        {
-            private readonly PipeManager _pipeManager;
-            private readonly List<IPipe> _pipes;
+    private class PipeList
+    {
+        private readonly PipeManager _pipeManager;
+        private readonly List<IPipe> _pipes;
 
 
-            public PipeList(PipeManager pipeManager, IEnumerable<IPipe> pipes)
-            {
-                _pipeManager = pipeManager;
-                _pipes = pipes.OrderByDescending(x => x.Priority).ToList();
+        public PipeList(PipeManager pipeManager, IEnumerable<IPipe> pipes)
+        {
+            _pipeManager = pipeManager;
+            _pipes = pipes.OrderByDescending(x => x.Priority).ToList();
 
 
-                ReloadEnabledPipes();
-            }
+            ReloadEnabledPipes();
+        }
 
 
-            public IList<IPipe> EnabledPipes { get; private set; } = default!;
+        public IList<IPipe> EnabledPipes { get; private set; } = default!;
 
 
-            internal void ReloadEnabledPipes()
-            {
-                EnabledPipes = _pipes.Where(_pipeManager.IsPipeEnabled).ToList();
-            }
+        internal void ReloadEnabledPipes()
+        {
+            EnabledPipes = _pipes.Where(_pipeManager.IsPipeEnabled).ToList();
         }
         }
     }
     }
 }
 }

部分文件因为文件数量过多而无法显示