├── .github
└── workflows
│ └── build-release.yml
├── .gitignore
├── DispatchR.sln
├── DispatchR.sln.DotSettings.user
├── LICENSE
├── README.md
├── benchmark
└── results
│ ├── notification-stable.png
│ ├── stream-with-pipeline-stable.png
│ ├── stream-without-pipeline-stable.png
│ ├── with-pipeline-stable.png
│ └── without-pipeline-stable.png
└── src
├── Benchmark
├── Benchmark.csproj
├── Notification
│ ├── MultiHandlers
│ │ ├── MultiHandler0.cs
│ │ ├── MultiHandler1.cs
│ │ ├── MultiHandler2.cs
│ │ └── MultiHandlersNotification.cs
│ ├── MultiHandlersAsync
│ │ ├── MultiHandlerAsync0.cs
│ │ ├── MultiHandlerAsync1.cs
│ │ ├── MultiHandlerAsync2.cs
│ │ └── MultiHandlersAsyncNotification.cs
│ ├── NotificationBenchmarks.cs
│ └── SingleHandler
│ │ ├── SingleHandler.cs
│ │ └── SingleHandlerNotification.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── SendRequest
│ ├── DispatchRCommands.cs
│ ├── MediatRCommands.cs
│ ├── MediatRVsDispatchRBenchmark.cs
│ ├── MediatRVsDispatchRWithPipelineBenchmark.cs
│ └── MediatSGCommands.cs
├── StreamRequest
│ ├── StreamDispatchRCommands.cs
│ ├── StreamMediatRCommands.cs
│ ├── StreamMediatRVsDispatchRBenchmark.cs
│ ├── StreamMediatRVsDispatchRWithPipelineBenchmark.cs
│ └── StreamMediatSGCommands.cs
├── appsettings.Development.json
└── appsettings.json
├── DispatchR
├── DispatchR.csproj
├── DispatchRServiceCollection.cs
└── Requests
│ ├── IMediator.cs
│ ├── Notification
│ ├── INotification.cs
│ └── INotificationHandler.cs
│ ├── Send
│ ├── IPipelineBehavior.cs
│ ├── IRequest.cs
│ └── IRequestHandler.cs
│ └── Stream
│ ├── IStreamPipelineBehavior.cs
│ ├── IStreamRequest.cs
│ └── IStreamRequestHandler.cs
└── Sample
├── BigFile.txt
├── DispatchR
├── Notification
│ ├── MultiHandlersNotification.cs
│ ├── NotificationOneHandler.cs
│ ├── NotificationThreeHandler.cs
│ └── NotificationTwoHandler.cs
├── SendRequest
│ ├── FirstPipelineBehavior.cs
│ ├── Ping.cs
│ ├── PingHandler.cs
│ └── SecondPipelineBehavior.cs
└── StreamRequest
│ ├── CounterPipelineStreamHandler.cs
│ ├── CounterStreamHandler.cs
│ └── CounterStreamRequest.cs
├── MediatR
├── Notification
│ ├── MultiHandlersNotification.cs
│ ├── NotificationOneHandler.cs
│ ├── NotificationThreeHandler.cs
│ └── NotificationTwoHandler.cs
├── SendRequest
│ ├── FirstPipelineBehavior.cs
│ ├── Ping.cs
│ ├── PingHandler.cs
│ └── SecondPipelineBehavior.cs
└── StreamRequest
│ ├── CounterPipelineStreamHandler.cs
│ ├── CounterStreamHandler.cs
│ └── CounterStreamRequest.cs
├── Program.cs
├── Properties
└── launchSettings.json
├── Sample.csproj
├── Sample.http
├── appsettings.Development.json
└── appsettings.json
/.github/workflows/build-release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | matrix:
12 | os: [ubuntu-latest]
13 | fail-fast: false
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Setup dotnet
22 | uses: actions/setup-dotnet@v4
23 | with:
24 | dotnet-version: '9.x'
25 |
26 | - name: Restore
27 | run: dotnet restore src/DispatchR/DispatchR.csproj
28 |
29 | - name: Build
30 | run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore
31 |
32 | - name: Extract version from tag
33 | id: get_version
34 | run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
35 |
36 | - name: Pack project
37 | run: dotnet pack src/DispatchR/DispatchR.csproj --configuration Release --no-build -o ./nupkgs /p:PackageVersion=${{ steps.get_version.outputs.version }}
38 |
39 | - name: Push to NuGet
40 | run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | .idea/
4 | /packages/
5 | riderModule.iml
6 | /_ReSharper.Caches/
7 | BenchmarkDotNet.Artifacts/
--------------------------------------------------------------------------------
/DispatchR.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7F7601D5-C62E-4EA3-8B71-E946A62B4529}"
4 | EndProject
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{89F559F6-C217-4D24-9A2F-DF25AE215A7C}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR", "src\DispatchR\DispatchR.csproj", "{9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}"
8 | EndProject
9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "src\Sample\Sample.csproj", "{3EB8B2BF-4228-408D-93D8-5280AAB438A0}"
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "src\Benchmark\Benchmark.csproj", "{92588047-1FE1-429A-8679-7B4FB39381F5}"
12 | EndProject
13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31F27C13-121E-4784-B06B-0FEF19817791}"
14 | ProjectSection(SolutionItems) = preProject
15 | README.md = README.md
16 | .gitignore = .gitignore
17 | EndProjectSection
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{0C407C1E-9E95-40A5-ABCA-52AACCC49C96}"
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{97F0685F-BF3D-4F1F-B195-E33DD15C071E}"
22 | ProjectSection(SolutionItems) = preProject
23 | .github\workflows\build-release.yml = .github\workflows\build-release.yml
24 | EndProjectSection
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {92588047-1FE1-429A-8679-7B4FB39381F5}.Release|Any CPU.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(NestedProjects) = preSolution
46 | {9A10D4A1-2E7F-4DE2-AC96-49E2914800D7} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C}
47 | {3EB8B2BF-4228-408D-93D8-5280AAB438A0} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C}
48 | {92588047-1FE1-429A-8679-7B4FB39381F5} = {89F559F6-C217-4D24-9A2F-DF25AE215A7C}
49 | {0C407C1E-9E95-40A5-ABCA-52AACCC49C96} = {31F27C13-121E-4784-B06B-0FEF19817791}
50 | {97F0685F-BF3D-4F1F-B195-E33DD15C071E} = {0C407C1E-9E95-40A5-ABCA-52AACCC49C96}
51 | EndGlobalSection
52 | EndGlobal
53 |
--------------------------------------------------------------------------------
/DispatchR.sln.DotSettings.user:
--------------------------------------------------------------------------------
1 |
2 | ForceIncluded
3 | ForceIncluded
4 | ForceIncluded
5 | ForceIncluded
6 | ForceIncluded
7 | ForceIncluded
8 | ForceIncluded
9 | ForceIncluded
10 | ForceIncluded
11 | ForceIncluded
12 | ForceIncluded
13 | ForceIncluded
14 | ForceIncluded
15 | ForceIncluded
16 | ForceIncluded
17 | ForceIncluded
18 | ForceIncluded
19 | ForceIncluded
20 | ForceIncluded
21 | ForceIncluded
22 | ForceIncluded
23 | ForceIncluded
24 | ForceIncluded
25 | ForceIncluded
26 | ForceIncluded
27 | ForceIncluded
28 | ForceIncluded
29 | ForceIncluded
30 | ForceIncluded
31 | ForceIncluded
32 | ForceIncluded
33 | ForceIncluded
34 | ForceIncluded
35 | ForceIncluded
36 | ForceIncluded
37 | ForceIncluded
38 | ForceIncluded
39 | ForceIncluded
40 | ForceIncluded
41 | ForceIncluded
42 | ForceIncluded
43 | ForceIncluded
44 | ForceIncluded
45 | ForceIncluded
46 | ForceIncluded
47 | ForceIncluded
48 | ForceIncluded
49 | ForceIncluded
50 | ForceIncluded
51 | ForceIncluded
52 | ForceIncluded
53 | ForceIncluded
54 | ForceIncluded
55 | ForceIncluded
56 | ForceIncluded
57 | ForceIncluded
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Hasan Arab Borzo
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DispatchR 🚀
2 |
3 | 
4 | [](https://www.nuget.org/packages/DispatchR.Mediator)
5 | [](https://www.nuget.org/packages/DispatchR.Mediator)
6 |
7 | ### A High-Performance Mediator Implementation for .NET :trollface:
8 | ** *Minimal memory footprint. Blazing-fast execution.* **
9 |
10 | > [!NOTE]
11 | > If you're curious to see the power of this library, [check out the benchmark](https://github.com/hasanxdev/DispatchR?tab=readme-ov-file#-bechmark-result) comparing MediatR vs Mediator Source Generator vs DispatchR.
12 |
13 | ## ⚡ Key Features
14 | - Built entirely on top of Dependency Injection
15 | - Zero runtime reflection after registration
16 | - Choose your handler return type: `Task`, `ValueTask`, or `Synchronous Method`
17 | - Allocates nothing on the heap — ideal for high-throughput scenarios
18 | - Outperforms existing solutions in most real-world benchmarks
19 | - Seamlessly compatible with MediatR — migrate with minimal effort
20 | - Currently supports
21 | 1. Simple Request:
22 | 1. `IRequest`
23 | 2. `IRequestHandler`
24 | 3. `IPipelineBehavior`
25 | 2. Stream Request:
26 | 1. `IStreamRequest`
27 | 2. `IStreamRequestHandler`
28 | 3. `IStreamPipelineBehavior`
29 | 3. Notifications:
30 | 1. `INotification`
31 | 2. `INotificationHandler`
32 | > :bulb: **Tip:** *If you're looking for a mediator with the raw performance of hand-written code, DispatchR is built for you.*
33 |
34 | # Syntax Comparison: DispatchR vs MediatR
35 |
36 | ###### In the following, you will see the key differences and implementation details between MediatR and DispatchR.
37 |
38 | ## Request Definition
39 |
40 | ### MediatR
41 | ```csharp
42 | public sealed class PingMediatR : IRequest { }
43 | ```
44 |
45 | ### DispatchR
46 | 1. Sending `TRequest` to `IRequest`
47 | 2. Precise selection of output for both `async` and `sync` handlers
48 | 1. Ability to choose between `Task` and `ValueTask`
49 |
50 | ```csharp
51 | public sealed class PingDispatchR : IRequest> { }
52 | ```
53 |
54 | ## Handler Definition
55 |
56 | ### MediatR
57 | ```csharp
58 | public sealed class PingHandlerMediatR : IRequestHandler
59 | {
60 | public Task Handle(PingMediatR request, CancellationToken cancellationToken)
61 | {
62 | return Task.FromResult(0);
63 | }
64 | }
65 | ```
66 |
67 | ### DispatchR (Don't change)
68 |
69 | ```csharp
70 | public sealed class PingHandlerDispatchR : IRequestHandler>
71 | {
72 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken)
73 | {
74 | return ValueTask.FromResult(0);
75 | }
76 | }
77 | ```
78 |
79 | ## Pipeline Behavior
80 |
81 | ### MediatR
82 | ```csharp
83 | public sealed class LoggingBehaviorMediat : IPipelineBehavior
84 | {
85 | public Task Handle(PingMediatR request, RequestHandlerDelegate next, CancellationToken cancellationToken)
86 | {
87 | return next(cancellationToken);
88 | }
89 | }
90 | ```
91 |
92 | ### DispatchR
93 | 1. Use ___Chain of Responsibility___ pattern
94 |
95 | ```csharp
96 | public sealed class LoggingBehaviorDispatchR : IPipelineBehavior>
97 | {
98 | public required IRequestHandler> NextPipeline { get; set; }
99 |
100 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken)
101 | {
102 | return NextPipeline.Handle(request, cancellationToken);
103 | }
104 | }
105 | ```
106 |
107 | ## Summary
108 |
109 | - **DispatchR** lets the request itself define the return type.
110 | - **No runtime reflection** in DispatchR — it's optimized for performance.
111 | - **No static behavior chains** — pipelines are chained via DI and handler wiring.
112 | - **Supports `void`, `Task`, or `ValueTask`** as return types.
113 |
114 | Ideal for high-performance .NET applications.
115 |
116 | ## Stream Request Definition
117 |
118 | ### MediatR Stream
119 | ```csharp
120 | public sealed class CounterStreamRequestMediatR : IStreamRequest { }
121 | ```
122 |
123 | ### DispatchR
124 | 1. Sending `TRequest` to `IStreamRequest`
125 |
126 | ```csharp
127 | public sealed class CounterStreamRequestDispatchR : IStreamRequest> { }
128 | ```
129 |
130 | ## Stream Handler Definition
131 |
132 | ### Stream Handler MediatR
133 | ```csharp
134 | public sealed class CounterStreamHandlerMediatR : IStreamRequestHandler
135 | {
136 | public async IAsyncEnumerable Handle(CounterStreamRequestMediatR request, CancellationToken cancellationToken)
137 | {
138 | yield return 1;
139 | }
140 | }
141 | ```
142 |
143 | ### Stream Handler DispatchR (Don't change)
144 |
145 | ```csharp
146 | public sealed class CounterStreamHandlerDispatchR : IStreamRequestHandler
147 | {
148 | public async IAsyncEnumerable Handle(CounterStreamHandlerDispatchR request, CancellationToken cancellationToken)
149 | {
150 | yield return 1;
151 | }
152 | }
153 | ```
154 |
155 | ## Stream Pipeline Behavior
156 |
157 | ### Stream Pipeline MediatR
158 | ```csharp
159 | public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior
160 | {
161 | public async IAsyncEnumerable Handle(CounterStreamRequestMediatR request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken)
162 | {
163 | await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false))
164 | {
165 | yield return response;
166 | }
167 | }
168 | }
169 | ```
170 |
171 | ### Stream Pipeline DispatchR
172 | 1. Use ___Chain of Responsibility___ pattern
173 |
174 | ```csharp
175 | public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior
176 | {
177 | public required IStreamRequestHandler NextPipeline { get; set; }
178 |
179 | public async IAsyncEnumerable Handle(CounterStreamRequestDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken)
180 | {
181 | await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
182 | {
183 | yield return response;
184 | }
185 | }
186 | }
187 | ```
188 |
189 | ------------
190 | ## Notification
191 |
192 | ### Notification MediatR
193 | ```csharp
194 | public sealed record Event(Guid Id) : INotification;
195 |
196 | public sealed class EventHandler(ILogger logger) : INotificationHandler
197 | {
198 | public Task Handle(Event notification, CancellationToken cancellationToken)
199 | {
200 | logger.LogInformation("Received notification");
201 | return Task.CompletedTask;
202 | }
203 | }
204 | ```
205 |
206 | ### Stream Pipeline DispatchR
207 | 1. Use ___ValueTask___
208 |
209 | ```csharp
210 | public sealed record Event(Guid Id) : INotification;
211 |
212 | public sealed class EventHandler(ILogger logger) : INotificationHandler
213 | {
214 | public ValueTask Handle(Event notification, CancellationToken cancellationToken)
215 | {
216 | logger.LogInformation("Received notification");
217 | return ValueTask.CompletedTask;
218 | }
219 | }
220 | ```
221 |
222 | # ⚡ How DispatchR Achieves High Performance
223 |
224 | ###### DispatchR is designed with one goal in mind: **maximize performance with minimal memory usage**. Here's how it accomplishes that:
225 |
226 | ## What Happens Inside the `Send` Method?
227 |
228 | ```csharp
229 | public TResponse Send(IRequest request,
230 | CancellationToken cancellationToken) where TRequest : class, IRequest, new()
231 | {
232 | return serviceProvider
233 | .GetRequiredService>()
234 | .Handle(Unsafe.As(request), cancellationToken);
235 | }
236 | ```
237 |
238 | ## What Happens Inside the `CreateStream` Method?
239 |
240 | ```csharp
241 | public IAsyncEnumerable CreateStream(IStreamRequest request,
242 | CancellationToken cancellationToken) where TRequest : class, IStreamRequest, new()
243 | {
244 | return serviceProvider.GetRequiredService>()
245 | .Handle(Unsafe.As(request), cancellationToken);
246 | }
247 | ```
248 | **Only the handler is resolved and directly invoked!**
249 |
250 | ## What Happens Inside the `Publish` Method?
251 |
252 | ```csharp
253 | public async ValueTask Publish(TNotification request, CancellationToken cancellationToken) where TNotification : INotification
254 | {
255 | var notificationsInDi = serviceProvider.GetRequiredService>>();
256 |
257 | var notifications = Unsafe.As[]>(notificationsInDi);
258 | foreach (var notification in notifications)
259 | {
260 | var valueTask = notification.Handle(request, cancellationToken);
261 | if (valueTask.IsCompletedSuccessfully is false) // <-- Handle sync notifications
262 | {
263 | await valueTask;
264 | }
265 | }
266 | }
267 | ```
268 |
269 | But the real magic happens behind the scenes when DI resolves the handler dependency:
270 | > 💡 __Tips:__
271 | > 1. *We cache the handler using DI, so in scoped scenarios, the object is constructed only once and reused afterward.*
272 | >
273 | > 2. *In terms of Dependency Injection (DI), everything in Requests is an IRequestHandler, it's just the keys that differ.
274 | When you request a specific key, a set of 1+N objects is returned: the first one is the actual handler, and the rest are the pipeline behaviors.*
275 |
276 | ```csharp
277 | services.AddScoped(handlerInterface, sp =>
278 | {
279 | var pipelinesWithHandler = Unsafe
280 | .As(sp.GetKeyedServices(key));
281 |
282 | IRequestHandler lastPipeline = pipelinesWithHandler[0];
283 | for (int i = 1; i < pipelinesWithHandler.Length; i++)
284 | {
285 | var pipeline = pipelinesWithHandler[i];
286 | pipeline.SetNext(lastPipeline);
287 | lastPipeline = pipeline;
288 | }
289 |
290 | return lastPipeline;
291 | });
292 | ```
293 |
294 | This elegant design chains pipeline behaviors at resolution time — no static lists, no reflection, no magic.
295 |
296 | ## 🪴 How to use?
297 | It's simple! Just use the following code:
298 | ```csharp
299 | builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: true, withNotifications: true);
300 | ```
301 | This code will automatically register all pipelines by default. If you need to register them in a specific order, you can either add them manually or write your own reflection logic:
302 | ```csharp
303 | builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: false, withNotifications: false);
304 | builder.Services.AddScoped, PipelineBehavior>();
305 | builder.Services.AddScoped, ValidationBehavior>();
306 | builder.Services.AddScoped, ValidationBehavior>();
307 | builder.Services.AddScoped, EventHandler>();
308 | ```
309 | ### 💡 Key Notes:
310 | 1. Automatic pipeline and notification registration is enabled by default
311 | 2. Manual registration allows for custom pipeline or notification ordering
312 | 3. You can implement custom reflection if needed
313 |
314 | ## ✨ How to install?
315 | ```
316 | dotnet add package DispatchR.Mediator --version 1.2.0
317 | ```
318 |
319 | # 🧪 Bechmark Result:
320 | > [!IMPORTANT]
321 | > This benchmark was conducted using MediatR version 12.5.0 and the stable release of Mediator Source Generator, version 2.1.7.
322 | Version 3 of Mediator Source Generator was excluded due to significantly lower performance.
323 |
324 | ### Send Request
325 | #### 1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline
326 | 
327 | #### 2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline
328 | 
329 |
330 | ### Stream Request
331 | #### 1. MediatR vs Mediator Source Generator vs DispatchR With Pipeline
332 | 
333 | #### 2. MediatR vs Mediator Source Generator vs DispatchR Without Pipeline
334 | 
335 |
336 | ### Notification
337 | #### 1. MediatR vs Mediator Source Generator vs DispatchR
338 | 
339 |
340 | ## ✨ Contribute & Help Grow This Package! ✨
341 | We welcome contributions to make this package even better! ❤️
342 | - Found a bug? → Open an issue
343 | - Have an idea? → Suggest a feature
344 | - Want to code? → Submit a PR
345 |
346 | Let's build something amazing together! 🚀
--------------------------------------------------------------------------------
/benchmark/results/notification-stable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/notification-stable.png
--------------------------------------------------------------------------------
/benchmark/results/stream-with-pipeline-stable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/stream-with-pipeline-stable.png
--------------------------------------------------------------------------------
/benchmark/results/stream-without-pipeline-stable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/stream-without-pipeline-stable.png
--------------------------------------------------------------------------------
/benchmark/results/with-pipeline-stable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/with-pipeline-stable.png
--------------------------------------------------------------------------------
/benchmark/results/without-pipeline-stable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasanxdev/DispatchR/6bd968e73a4172c4da02be300587ff26a7e607f3/benchmark/results/without-pipeline-stable.png
--------------------------------------------------------------------------------
/src/Benchmark/Benchmark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | true
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlers/MultiHandler0.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlers;
4 |
5 | public sealed class MultiHandler0
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default;
11 |
12 | Task MediatR.INotificationHandler.Handle(
13 | MultiHandlersNotification notification,
14 | CancellationToken cancellationToken
15 | ) => Task.CompletedTask;
16 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlers/MultiHandler1.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlers;
4 |
5 | public sealed class MultiHandler1
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default;
11 |
12 | Task MediatR.INotificationHandler.Handle(
13 | MultiHandlersNotification notification,
14 | CancellationToken cancellationToken
15 | ) => Task.CompletedTask;
16 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlers/MultiHandler2.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlers;
4 |
5 | public sealed class MultiHandler2
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public ValueTask Handle(MultiHandlersNotification notification, CancellationToken cancellationToken) => default;
11 |
12 | Task MediatR.INotificationHandler.Handle(
13 | MultiHandlersNotification notification,
14 | CancellationToken cancellationToken
15 | ) => Task.CompletedTask;
16 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlers/MultiHandlersNotification.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlers;
4 |
5 | public sealed record MultiHandlersNotification(Guid Id) : INotification, MediatR.INotification,
6 | DispatchR.Requests.Notification.INotification;
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync0.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlersAsync;
4 |
5 | public sealed class MultiHandlerAsync0
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) =>
11 | await Task.Yield();
12 |
13 | async Task MediatR.INotificationHandler.Handle(
14 | MultiHandlersAsyncNotification notification,
15 | CancellationToken cancellationToken
16 | ) => await Task.Yield();
17 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync1.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlersAsync;
4 |
5 | public sealed class MultiHandlerAsync1
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) =>
11 | await Task.Yield();
12 |
13 | async Task MediatR.INotificationHandler.Handle(
14 | MultiHandlersAsyncNotification notification,
15 | CancellationToken cancellationToken
16 | ) => await Task.Yield();
17 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlersAsync/MultiHandlerAsync2.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlersAsync;
4 |
5 | public sealed class MultiHandlerAsync2
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public async ValueTask Handle(MultiHandlersAsyncNotification notification, CancellationToken cancellationToken) =>
11 | await Task.Yield();
12 |
13 | async Task MediatR.INotificationHandler.Handle(
14 | MultiHandlersAsyncNotification notification,
15 | CancellationToken cancellationToken
16 | ) => await Task.Yield();
17 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/MultiHandlersAsync/MultiHandlersAsyncNotification.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.MultiHandlersAsync;
4 |
5 | public sealed record MultiHandlersAsyncNotification(Guid Id) : INotification, MediatR.INotification,
6 | DispatchR.Requests.Notification.INotification;
--------------------------------------------------------------------------------
/src/Benchmark/Notification/NotificationBenchmarks.cs:
--------------------------------------------------------------------------------
1 | using Benchmark.Notification.MultiHandlers;
2 | using Benchmark.Notification.MultiHandlersAsync;
3 | using Benchmark.Notification.SingleHandler;
4 | using BenchmarkDotNet.Attributes;
5 | using BenchmarkDotNet.Order;
6 | using DispatchR;
7 |
8 | namespace Benchmark.Notification;
9 |
10 | [MemoryDiagnoser]
11 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
12 | public class NotificationBenchmarks
13 | {
14 | private IServiceProvider _serviceProvider;
15 | private IServiceScope _serviceScope;
16 | private Mediator.IMediator _mediator;
17 | private DispatchR.Requests.IMediator _dispatchR;
18 | private Mediator.Mediator _concreteMediator;
19 | private MediatR.IMediator _mediatr;
20 | private SingleHandler.SingleHandler _singleHandler;
21 | private SingleHandlerNotification _singleHandlerNotification;
22 | private MultiHandler0 _multiHandler0;
23 | private MultiHandler1 _multiHandler1;
24 | private MultiHandler2 _multiHandler2;
25 | private MultiHandlersNotification _multiHandlersNotification;
26 | private MultiHandlerAsync0 _multiHandlerAsync0;
27 | private MultiHandlerAsync1 _multiHandlerAsync1;
28 | private MultiHandlerAsync2 _multiHandlerAsync2;
29 | private MultiHandlersAsyncNotification _multiHandlersAsyncNotification;
30 |
31 | public enum ScenarioType
32 | {
33 | SingleHandlerSync,
34 | MultiHandlersSync,
35 | MultiHandlersAsync,
36 | }
37 |
38 | [ParamsAllValues]
39 | public ScenarioType Scenario { get; set; }
40 |
41 | [GlobalSetup]
42 | public void Setup()
43 | {
44 | var services = new ServiceCollection();
45 | services.AddMediator(opts =>
46 | {
47 | opts.ServiceLifetime = ServiceLifetime.Scoped;
48 | });
49 | services.AddDispatchR(typeof(SingleHandler.SingleHandler).Assembly);
50 | services.AddMediatR(opts =>
51 | {
52 | opts.Lifetime = ServiceLifetime.Scoped;
53 | opts.RegisterServicesFromAssembly(typeof(SingleHandler.SingleHandler).Assembly);
54 | });
55 |
56 | _serviceProvider = services.BuildServiceProvider();
57 | _serviceScope = _serviceProvider.CreateScope();
58 | _serviceProvider = _serviceScope.ServiceProvider;
59 |
60 | _mediator = _serviceProvider.GetRequiredService();
61 | _dispatchR = _serviceProvider.GetRequiredService();
62 | _concreteMediator = _serviceProvider.GetRequiredService();
63 | _mediatr = _serviceProvider.GetRequiredService();
64 |
65 | _singleHandler = _serviceProvider.GetRequiredService();
66 | _singleHandlerNotification = new(Guid.NewGuid());
67 |
68 | _multiHandler0 = _serviceProvider.GetRequiredService();
69 | _multiHandler1 = _serviceProvider.GetRequiredService();
70 | _multiHandler2 = _serviceProvider.GetRequiredService();
71 | _multiHandlersNotification = new(Guid.NewGuid());
72 |
73 | _multiHandlerAsync0 = _serviceProvider.GetRequiredService();
74 | _multiHandlerAsync1 = _serviceProvider.GetRequiredService();
75 | _multiHandlerAsync2 = _serviceProvider.GetRequiredService();
76 | _multiHandlersAsyncNotification = new(Guid.NewGuid());
77 | }
78 |
79 | [GlobalCleanup]
80 | public void Cleanup()
81 | {
82 | if (_serviceScope is not null)
83 | _serviceScope.Dispose();
84 | else
85 | (_serviceProvider as IDisposable)?.Dispose();
86 | }
87 |
88 | [Benchmark]
89 | public Task Publish_Notification_MediatR()
90 | {
91 | return Scenario switch
92 | {
93 | ScenarioType.SingleHandlerSync => _mediatr.Publish(_singleHandlerNotification),
94 | ScenarioType.MultiHandlersSync => _mediatr.Publish(_multiHandlersNotification),
95 | ScenarioType.MultiHandlersAsync => _mediatr.Publish(_multiHandlersAsyncNotification),
96 | };
97 | }
98 |
99 | [Benchmark]
100 | public ValueTask Publish_Notification_DispatchR()
101 | {
102 | return Scenario switch
103 | {
104 | ScenarioType.SingleHandlerSync => _dispatchR.Publish(_singleHandlerNotification, CancellationToken.None),
105 | ScenarioType.MultiHandlersSync => _dispatchR.Publish(_multiHandlersNotification, CancellationToken.None),
106 | ScenarioType.MultiHandlersAsync => _dispatchR.Publish(_multiHandlersAsyncNotification, CancellationToken.None),
107 | };
108 | }
109 |
110 | [Benchmark]
111 | public ValueTask Publish_Notification_IMediator()
112 | {
113 | return Scenario switch
114 | {
115 | ScenarioType.SingleHandlerSync => _mediator.Publish(_singleHandlerNotification),
116 | ScenarioType.MultiHandlersSync => _mediator.Publish(_multiHandlersNotification),
117 | ScenarioType.MultiHandlersAsync => _mediator.Publish(_multiHandlersAsyncNotification),
118 | };
119 | }
120 |
121 | [Benchmark]
122 | public ValueTask Publish_Notification_Mediator()
123 | {
124 | return Scenario switch
125 | {
126 | ScenarioType.SingleHandlerSync => _concreteMediator.Publish(_singleHandlerNotification),
127 | ScenarioType.MultiHandlersSync => _concreteMediator.Publish(_multiHandlersNotification),
128 | ScenarioType.MultiHandlersAsync => _concreteMediator.Publish(_multiHandlersAsyncNotification),
129 | };
130 | }
131 |
132 | [Benchmark(Baseline = true)]
133 | public ValueTask Publish_Notification_Baseline()
134 | {
135 | switch (Scenario)
136 | {
137 | case ScenarioType.SingleHandlerSync:
138 | return _singleHandler.Handle(_singleHandlerNotification, default);
139 | case ScenarioType.MultiHandlersSync:
140 | _multiHandler0.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult();
141 | _multiHandler1.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult();
142 | _multiHandler2.Handle(_multiHandlersNotification, default).GetAwaiter().GetResult();
143 | return default;
144 | case ScenarioType.MultiHandlersAsync:
145 | return AwaitMultipleHandlersAsync();
146 | }
147 |
148 | return default;
149 |
150 | async ValueTask AwaitMultipleHandlersAsync()
151 | {
152 | await _multiHandlerAsync0.Handle(_multiHandlersAsyncNotification, default);
153 | await _multiHandlerAsync1.Handle(_multiHandlersAsyncNotification, default);
154 | await _multiHandlerAsync2.Handle(_multiHandlersAsyncNotification, default);
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/Benchmark/Notification/SingleHandler/SingleHandler.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.SingleHandler;
4 |
5 | public sealed class SingleHandler
6 | : INotificationHandler,
7 | MediatR.INotificationHandler,
8 | DispatchR.Requests.Notification.INotificationHandler
9 | {
10 | public ValueTask Handle(SingleHandlerNotification notification, CancellationToken cancellationToken) => default;
11 |
12 | Task MediatR.INotificationHandler.Handle(
13 | SingleHandlerNotification notification,
14 | CancellationToken cancellationToken
15 | ) => Task.CompletedTask;
16 | }
--------------------------------------------------------------------------------
/src/Benchmark/Notification/SingleHandler/SingleHandlerNotification.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.Notification.SingleHandler;
4 |
5 | public sealed record SingleHandlerNotification(Guid Id) : INotification, MediatR.INotification,
6 | DispatchR.Requests.Notification.INotification;
--------------------------------------------------------------------------------
/src/Benchmark/Program.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using Benchmark;
3 | using Benchmark.Notification;
4 | using Benchmark.SendRequest;
5 | using Benchmark.StreamRequest;
6 | using BenchmarkDotNet.Attributes;
7 | using BenchmarkDotNet.Columns;
8 | using BenchmarkDotNet.Configs;
9 | using BenchmarkDotNet.Reports;
10 | using BenchmarkDotNet.Running;
11 |
12 | BenchmarkSwitcher.FromTypes([
13 | typeof(MediatRVsDispatchBenchmark),
14 | typeof(MediatRVsDispatchWithPipelineRBenchmark),
15 | typeof(StreamMediatRVsDispatchBenchmark),
16 | typeof(StreamMediatRVsDispatchWithPipelineRBenchmark),
17 | typeof(NotificationBenchmarks),
18 | ]).Run(args, ManualConfig.Create(DefaultConfig.Instance)
19 | .AddColumn(new OperationsColumn())
20 | );
21 |
22 | public class OperationsColumn : IColumn
23 | {
24 | public string Id => nameof(OperationsColumn);
25 | public string ColumnName => "OpsCount";
26 | public bool AlwaysShow => true;
27 | public ColumnCategory Category => ColumnCategory.Custom;
28 | public int PriorityInCategory => -10;
29 | public bool IsNumeric => true;
30 | public UnitType UnitType => UnitType.Dimensionless;
31 | public string Legend => "Number of operations per invoke";
32 |
33 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase)
34 | {
35 | return benchmarkCase.Descriptor.WorkloadMethod
36 | .GetCustomAttributes(typeof(BenchmarkAttribute), false)
37 | .Cast()
38 | .FirstOrDefault()?.OperationsPerInvoke.ToString() ?? "1";
39 | }
40 |
41 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style)
42 | => GetValue(summary, benchmarkCase);
43 |
44 | public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase)
45 | {
46 | return true;
47 | }
48 |
49 | public bool IsAvailable(Summary summary) => true;
50 | public bool IsDefault(Summary summary) => false;
51 | }
--------------------------------------------------------------------------------
/src/Benchmark/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": false,
7 | "launchBrowser": false,
8 | "applicationUrl": "http://localhost:5126",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development"
11 | }
12 | },
13 | "https": {
14 | "commandName": "Project",
15 | "dotnetRunMessages": false,
16 | "launchBrowser": false,
17 | "applicationUrl": "https://localhost:7100;http://localhost:5126",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Benchmark/SendRequest/DispatchRCommands.cs:
--------------------------------------------------------------------------------
1 | using DispatchR.Requests;
2 | using DispatchR.Requests.Send;
3 |
4 | namespace Benchmark.SendRequest;
5 |
6 | public sealed record PingDispatchR : IRequest> { }
7 |
8 | public sealed record PingDispatchRWithOutHandler : IRequest> { }
9 |
10 | public sealed class PingHandlerDispatchR : IRequestHandler>
11 | {
12 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken)
13 | {
14 | return ValueTask.FromResult(0);
15 | }
16 | }
17 |
18 | public sealed class LoggingBehaviorDispatchR : IPipelineBehavior>
19 | {
20 | public required IRequestHandler> NextPipeline { get; set; }
21 |
22 | public ValueTask Handle(PingDispatchR request, CancellationToken cancellationToken)
23 | {
24 | return NextPipeline.Handle(request, cancellationToken);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Benchmark/SendRequest/MediatRCommands.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 |
3 | namespace Benchmark.SendRequest;
4 |
5 | public sealed class PingMediatR : IRequest { }
6 | public sealed class PingMediatRWithOutHandler : IRequest { }
7 |
8 | public sealed class PingHandlerMediatR : IRequestHandler
9 | {
10 | public Task Handle(PingMediatR request, CancellationToken cancellationToken)
11 | {
12 | return Task.FromResult(0);
13 | }
14 | }
15 |
16 | public sealed class LoggingBehaviorMediat : IPipelineBehavior
17 | {
18 | public Task Handle(PingMediatR request, RequestHandlerDelegate next, CancellationToken cancellationToken)
19 | {
20 | return next(cancellationToken);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Benchmark/SendRequest/MediatRVsDispatchRBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Order;
3 | using DispatchR;
4 | using IMediator = MediatR.IMediator;
5 |
6 | namespace Benchmark.SendRequest;
7 |
8 | [MemoryDiagnoser]
9 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
10 | public class MediatRVsDispatchBenchmark
11 | {
12 | private const int TotalSendRequests = 5_000;
13 | private IServiceScope _serviceScopeForMediatRWithoutPipeline;
14 | private IServiceScope _serviceScopeForMediatSgWithoutPipeline;
15 | private IServiceScope _serviceScopeForDispatchRWithoutPipeline;
16 | private DispatchR.Requests.IMediator _dispatchRWithoutPipeline;
17 | private IMediator _mediatRWithoutPipeline;
18 | private Mediator.IMediator _mediatSgWithoutPipeline;
19 | private static readonly PingDispatchR StaticDispatchR = new();
20 | private static readonly PingMediatR StaticPingMediatR = new();
21 | private static readonly PingMediatSG StaticPingMediatSg = new();
22 | private static readonly PingDispatchRWithOutHandler StaticDispatchRRequestWithOutHandler = new();
23 | private static readonly PingMediatRWithOutHandler StaticPingMediatRWithOutHandler = new();
24 | private static readonly PingMediatSGWithOutHandler StaticPingMediatSgWithOutHandler = new();
25 | private static List ScopesForMediatRWithoutPipeline { get; set; } = new(TotalSendRequests);
26 | private static List ScopesForMediatSgWithoutPipeline { get; set; } = new(TotalSendRequests);
27 | private static List ScopesForDispatchRWithoutPipeline { get; set; } = new(TotalSendRequests);
28 |
29 | [GlobalSetup]
30 | public void Setup()
31 | {
32 | var withoutPipelineServices = new ServiceCollection();
33 | withoutPipelineServices.AddMediatR(cfg =>
34 | {
35 | cfg.Lifetime = ServiceLifetime.Scoped;
36 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly);
37 | });
38 | withoutPipelineServices.AddMediator(opts =>
39 | {
40 | opts.ServiceLifetime = ServiceLifetime.Scoped;
41 | });
42 | withoutPipelineServices.AddDispatchR(typeof(PingDispatchR).Assembly, withPipelines: false);
43 | var buildServicesWithoutPipeline = withoutPipelineServices.BuildServiceProvider();
44 | _dispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
45 | _mediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
46 | _mediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
47 | _serviceScopeForMediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
48 | _serviceScopeForMediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
49 | _serviceScopeForDispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
50 | ScopesForMediatRWithoutPipeline.Clear();
51 | ScopesForMediatSgWithoutPipeline.Clear();
52 | ScopesForDispatchRWithoutPipeline.Clear();
53 | Parallel.For(0, TotalSendRequests, i =>
54 | {
55 | ScopesForMediatRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
56 | ScopesForDispatchRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
57 | ScopesForMediatSgWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
58 | });
59 | }
60 |
61 | [GlobalCleanup]
62 | public void Cleanup()
63 | {
64 | _serviceScopeForMediatRWithoutPipeline.Dispose();
65 | _serviceScopeForMediatSgWithoutPipeline.Dispose();
66 | _serviceScopeForDispatchRWithoutPipeline.Dispose();
67 | _dispatchRWithoutPipeline = null!;
68 | _mediatRWithoutPipeline = null!;
69 | _mediatSgWithoutPipeline = null!;
70 | Parallel.ForEach(ScopesForMediatRWithoutPipeline, scope => scope.Dispose());
71 | Parallel.ForEach(ScopesForMediatSgWithoutPipeline, scope => scope.Dispose());
72 | Parallel.ForEach(ScopesForDispatchRWithoutPipeline, scope => scope.Dispose());
73 | }
74 |
75 | #region SendRequest_With_ExistRequest_ExistMediator_WithOutHandler
76 |
77 | [Benchmark(Baseline = true)]
78 | public Task MediatR___SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
79 | {
80 | try
81 | {
82 | return _mediatRWithoutPipeline.Send(StaticPingMediatRWithOutHandler, CancellationToken.None);
83 | }
84 | catch
85 | {
86 | return Task.FromResult(0);
87 | }
88 | }
89 |
90 | [Benchmark]
91 | public ValueTask MediatSG__SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
92 | {
93 | try
94 | {
95 | return _mediatSgWithoutPipeline.Send(StaticPingMediatSgWithOutHandler, CancellationToken.None);
96 | }
97 | catch
98 | {
99 | return ValueTask.FromResult(0);
100 | }
101 | }
102 |
103 | [Benchmark]
104 | public ValueTask DispatchR_SendRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
105 | {
106 | try
107 | {
108 | return _dispatchRWithoutPipeline.Send(StaticDispatchRRequestWithOutHandler, CancellationToken.None);
109 | }
110 | catch
111 | {
112 | return ValueTask.FromResult(0);
113 | }
114 | }
115 |
116 | #endregion
117 |
118 | #region SendRequest_With_ExistRequest_ExistMediator
119 |
120 | [Benchmark]
121 | public Task MediatR___SendRequest_With_ExistRequest_ExistMediator()
122 | {
123 | return _mediatRWithoutPipeline.Send(StaticPingMediatR, CancellationToken.None);
124 | }
125 |
126 | [Benchmark]
127 | public ValueTask MediatSG__SendRequest_With_ExistRequest_ExistMediator()
128 | {
129 | return _mediatSgWithoutPipeline.Send(StaticPingMediatSg, CancellationToken.None);
130 | }
131 |
132 | [Benchmark]
133 | public ValueTask DispatchR_SendRequest_With_ExistRequest_ExistMediator()
134 | {
135 | return _dispatchRWithoutPipeline.Send(StaticDispatchR, CancellationToken.None);
136 | }
137 |
138 | #endregion
139 |
140 | #region SendRequest_With_ExistRequest_GetMediator
141 |
142 | [Benchmark]
143 | public async Task MediatR___SendRequest_With_ExistRequest_GetMediator()
144 | {
145 | var result = await _serviceScopeForMediatRWithoutPipeline
146 | .ServiceProvider
147 | .GetRequiredService()
148 | .Send(StaticPingMediatR, CancellationToken.None);
149 |
150 | return result;
151 | }
152 |
153 | [Benchmark]
154 | public async ValueTask MediatSG__SendRequest_With_ExistRequest_GetMediator()
155 | {
156 | var result = await _serviceScopeForMediatSgWithoutPipeline
157 | .ServiceProvider
158 | .GetRequiredService()
159 | .Send(StaticPingMediatSg, CancellationToken.None);
160 |
161 | return result;
162 | }
163 |
164 | [Benchmark]
165 | public ValueTask DispatchR_SendRequest_With_ExistRequest_GetMediator()
166 | {
167 | return _serviceScopeForDispatchRWithoutPipeline
168 | .ServiceProvider
169 | .GetRequiredService()
170 | .Send(StaticDispatchR, CancellationToken.None);
171 | }
172 |
173 | #endregion
174 |
175 | #region SendRequest_With_ExistRequest_ExistMediator_Parallel
176 |
177 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
178 | public async Task MediatR___SendRequest_With_ExistRequest_ExistMediator_Parallel()
179 | {
180 | var result = 0;
181 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
182 | {
183 | result = await _mediatRWithoutPipeline.Send(StaticPingMediatR, CancellationToken.None);
184 | });
185 |
186 | return result;
187 | }
188 |
189 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
190 | public async Task MediatSG__SendRequest_With_ExistRequest_ExistMediator_Parallel()
191 | {
192 | var result = 0;
193 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
194 | {
195 | result = await _mediatSgWithoutPipeline.Send(StaticPingMediatSg, CancellationToken.None);
196 | });
197 |
198 | return result;
199 | }
200 |
201 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
202 | public async Task DispatchR_SendRequest_With_ExistRequest_ExistMediator_Parallel()
203 | {
204 | var result = 0;
205 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
206 | {
207 | result = await _dispatchRWithoutPipeline.Send(StaticDispatchR, CancellationToken.None);
208 | });
209 |
210 | return result;
211 | }
212 |
213 | #endregion
214 |
215 | #region SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel
216 |
217 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
218 | public async Task MediatR___SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
219 | {
220 | var result = 0;
221 | await Parallel.ForEachAsync(ScopesForMediatRWithoutPipeline, async (scope, ct) =>
222 | {
223 | result = await scope.ServiceProvider.GetRequiredService()
224 | .Send(StaticPingMediatR, CancellationToken.None);
225 | });
226 |
227 | return result;
228 | }
229 |
230 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
231 | public async Task MediatSG__SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
232 | {
233 | var result = 0;
234 | await Parallel.ForEachAsync(ScopesForMediatSgWithoutPipeline, async (scope, ct) =>
235 | {
236 | result = await scope.ServiceProvider.GetRequiredService()
237 | .Send(StaticPingMediatSg, CancellationToken.None);
238 | });
239 |
240 | return result;
241 | }
242 |
243 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
244 | public async Task DispatchR_SendRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
245 | {
246 | var result = 0;
247 | await Parallel.ForEachAsync(ScopesForDispatchRWithoutPipeline, async (scope, ct) =>
248 | {
249 | result = await scope.ServiceProvider.GetRequiredService()
250 | .Send(StaticDispatchR, CancellationToken.None);
251 | });
252 |
253 | return result;
254 | }
255 |
256 | #endregion
257 | }
--------------------------------------------------------------------------------
/src/Benchmark/SendRequest/MediatRVsDispatchRWithPipelineBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Order;
3 | using DispatchR;
4 | using Mediator;
5 | using IMediator = MediatR.IMediator;
6 |
7 | namespace Benchmark.SendRequest;
8 |
9 | [MemoryDiagnoser]
10 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
11 | public class MediatRVsDispatchWithPipelineRBenchmark
12 | {
13 | private const int TotalSendRequests = 5_000;
14 | private IServiceScope _serviceScopeForMediatRWithPipeline;
15 | private IServiceScope _serviceScopeForMediatSgWithPipeline;
16 | private IServiceScope _serviceScopeForDispatchRWithPipeline;
17 | private DispatchR.Requests.IMediator _dispatchRWithPipeline;
18 | private IMediator _mediatRWithPipeline;
19 | private Mediator.IMediator _mediatSgWithPipeline;
20 | private static readonly PingDispatchR StaticDispatchR = new();
21 | private static readonly PingMediatR StaticPingMediatR = new();
22 | private static readonly PingMediatSG StaticPingMediatSg = new();
23 | private static readonly PingDispatchRWithOutHandler StaticDispatchRRequestWithOutHandler = new();
24 | private static readonly PingMediatRWithOutHandler StaticPingMediatRWithOutHandler = new();
25 | private static readonly PingMediatSGWithOutHandler StaticPingMediatSgWithOutHandler = new();
26 | private static List ScopesForMediatRWithPipeline { get; set; } = new(TotalSendRequests);
27 | private static List ScopesForMediatSgWithPipeline { get; set; } = new(TotalSendRequests);
28 | private static List ScopesForDispatchRWithPipeline { get; set; } = new(TotalSendRequests);
29 |
30 | [GlobalSetup]
31 | public void Setup()
32 | {
33 | var withPipelineServices = new ServiceCollection();
34 |
35 | withPipelineServices.AddMediatR(cfg =>
36 | {
37 | cfg.Lifetime = ServiceLifetime.Scoped;
38 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly);
39 | });
40 | withPipelineServices.AddScoped, LoggingBehaviorMediat>();
41 |
42 | withPipelineServices.AddMediator((MediatorOptions options) =>
43 | {
44 | options.ServiceLifetime = ServiceLifetime.Scoped;
45 | // options.PipelineBehaviors = [typeof(LoggingBehaviorMediatSG)];
46 | });
47 | withPipelineServices.AddScoped, LoggingBehaviorMediatSG>();
48 |
49 | withPipelineServices.AddDispatchR(typeof(PingDispatchR).Assembly);
50 | var buildServicesWithoutPipeline = withPipelineServices.BuildServiceProvider();
51 | _dispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
52 | _mediatRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
53 | _mediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
54 | _serviceScopeForMediatRWithPipeline = buildServicesWithoutPipeline.CreateScope();
55 | _serviceScopeForMediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope();
56 | _serviceScopeForDispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope();
57 | ScopesForMediatRWithPipeline.Clear();
58 | ScopesForMediatSgWithPipeline.Clear();
59 | ScopesForDispatchRWithPipeline.Clear();
60 | Parallel.For(0, TotalSendRequests, i =>
61 | {
62 | ScopesForMediatRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
63 | ScopesForDispatchRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
64 | ScopesForMediatSgWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
65 | });
66 | }
67 |
68 | [GlobalCleanup]
69 | public void Cleanup()
70 | {
71 | _serviceScopeForMediatRWithPipeline.Dispose();
72 | _serviceScopeForMediatSgWithPipeline.Dispose();
73 | _serviceScopeForDispatchRWithPipeline.Dispose();
74 | _dispatchRWithPipeline = null!;
75 | _mediatRWithPipeline = null!;
76 | _mediatSgWithPipeline = null!;
77 | Parallel.ForEach(ScopesForMediatRWithPipeline, scope => scope.Dispose());
78 | Parallel.ForEach(ScopesForMediatSgWithPipeline, scope => scope.Dispose());
79 | Parallel.ForEach(ScopesForDispatchRWithPipeline, scope => scope.Dispose());
80 | }
81 |
82 | #region SendRequest_With_Pipeline_ExistRequest_ExistMediator_WithOutHandler
83 |
84 | [Benchmark(Baseline = true)]
85 | public Task MediatR___SendRequest_ExistRequest_ExistMediator_WithOut_Handler()
86 | {
87 | try
88 | {
89 | return _mediatRWithPipeline.Send(StaticPingMediatRWithOutHandler, CancellationToken.None);
90 | }
91 | catch
92 | {
93 | return Task.FromResult(0);
94 | }
95 | }
96 |
97 | [Benchmark]
98 | public ValueTask MediatSG__SendRequest_ExistRequest_ExistMediator_WithOut_Handler()
99 | {
100 | try
101 | {
102 | return _mediatSgWithPipeline.Send(StaticPingMediatSgWithOutHandler, CancellationToken.None);
103 | }
104 | catch
105 | {
106 | return ValueTask.FromResult(0);
107 | }
108 | }
109 |
110 | [Benchmark]
111 | public ValueTask DispatchR_SendRequest_ExistRequest_ExistMediator_WithOut_Handler()
112 | {
113 | try
114 | {
115 | return _dispatchRWithPipeline.Send(StaticDispatchRRequestWithOutHandler, CancellationToken.None);
116 | }
117 | catch
118 | {
119 | return ValueTask.FromResult(0);
120 | }
121 | }
122 |
123 | #endregion
124 |
125 | #region SendRequest_ExistRequest_ExistMediator
126 |
127 | [Benchmark]
128 | public Task MediatR___SendRequest_ExistRequest_ExistMediator()
129 | {
130 | return _mediatRWithPipeline.Send(StaticPingMediatR, CancellationToken.None);
131 | }
132 |
133 | [Benchmark]
134 | public ValueTask MediatSG__SendRequest_ExistRequest_ExistMediator()
135 | {
136 | return _mediatSgWithPipeline.Send(StaticPingMediatSg, CancellationToken.None);
137 | }
138 |
139 | [Benchmark]
140 | public ValueTask DispatchR_SendRequest_ExistRequest_ExistMediator()
141 | {
142 | return _dispatchRWithPipeline.Send(StaticDispatchR, CancellationToken.None);
143 | }
144 |
145 | #endregion
146 |
147 | #region SendRequest_ExistRequest_GetMediator
148 |
149 | [Benchmark]
150 | public Task MediatR___SendRequest_ExistRequest_GetMediator()
151 | {
152 | return _serviceScopeForMediatRWithPipeline
153 | .ServiceProvider
154 | .GetRequiredService()
155 | .Send(StaticPingMediatR, CancellationToken.None);
156 | }
157 |
158 | [Benchmark]
159 | public ValueTask MediatSG__SendRequest_ExistRequest_GetMediator()
160 | {
161 | return _serviceScopeForMediatSgWithPipeline
162 | .ServiceProvider
163 | .GetRequiredService()
164 | .Send(StaticPingMediatSg, CancellationToken.None);
165 | }
166 |
167 | [Benchmark]
168 | public ValueTask DispatchR_SendRequest_ExistRequest_GetMediator()
169 | {
170 | return _serviceScopeForDispatchRWithPipeline
171 | .ServiceProvider
172 | .GetRequiredService()
173 | .Send(StaticDispatchR, CancellationToken.None);
174 | }
175 |
176 | #endregion
177 |
178 | #region SendRequest_ExistRequest_ExistMediator_Parallel
179 |
180 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
181 | public async Task MediatR___SendRequest_ExistRequest_ExistMediator_Parallel()
182 | {
183 | var result = 0;
184 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
185 | {
186 | result = await _mediatRWithPipeline.Send(StaticPingMediatR, CancellationToken.None);
187 | });
188 |
189 | return result;
190 | }
191 |
192 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
193 | public async Task MediatSG__SendRequest_ExistRequest_ExistMediator_Parallel()
194 | {
195 | var result = 0;
196 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
197 | {
198 | result = await _mediatSgWithPipeline.Send(StaticPingMediatSg, CancellationToken.None);
199 | });
200 |
201 | return result;
202 | }
203 |
204 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
205 | public async Task DispatchR_SendRequest_ExistRequest_ExistMediator_Parallel()
206 | {
207 | var result = 0;
208 | await Parallel.ForAsync(0, TotalSendRequests, async (index, ct) =>
209 | {
210 | result = await _dispatchRWithPipeline.Send(StaticDispatchR, CancellationToken.None);
211 | });
212 |
213 | return result;
214 | }
215 |
216 | #endregion
217 |
218 | #region SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel
219 |
220 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
221 | public async Task MediatR___SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
222 | {
223 | var result = 0;
224 | await Parallel.ForEachAsync(ScopesForMediatRWithPipeline, async (scope, ct) =>
225 | {
226 | result = await scope.ServiceProvider.GetRequiredService()
227 | .Send(StaticPingMediatR, CancellationToken.None);
228 | });
229 |
230 | return result;
231 | }
232 |
233 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
234 | public async Task MediatSG__SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
235 | {
236 | var result = 0;
237 | await Parallel.ForEachAsync(ScopesForMediatSgWithPipeline, async (scope, ct) =>
238 | {
239 | result = await scope.ServiceProvider.GetRequiredService()
240 | .Send(StaticPingMediatSg, CancellationToken.None);
241 | });
242 |
243 | return result;
244 | }
245 |
246 | [Benchmark(OperationsPerInvoke = TotalSendRequests)]
247 | public async Task DispatchR_SendRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
248 | {
249 | var result = 0;
250 | await Parallel.ForEachAsync(ScopesForDispatchRWithPipeline, async (scope, ct) =>
251 | {
252 | result = await scope.ServiceProvider.GetRequiredService()
253 | .Send(StaticDispatchR, CancellationToken.None);
254 | });
255 |
256 | return result;
257 | }
258 |
259 | #endregion
260 | }
--------------------------------------------------------------------------------
/src/Benchmark/SendRequest/MediatSGCommands.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace Benchmark.SendRequest;
4 |
5 | public sealed class PingMediatSG : IRequest { }
6 | public sealed class PingMediatSGWithOutHandler : IRequest { }
7 |
8 | public sealed class PingHandlerMediatSG : IRequestHandler
9 | {
10 | public ValueTask Handle(PingMediatSG request, CancellationToken cancellationToken)
11 | {
12 | return ValueTask.FromResult(0);
13 | }
14 | }
15 |
16 | public sealed class LoggingBehaviorMediatSG : IPipelineBehavior
17 | {
18 | // version 2.x
19 | public ValueTask Handle(PingMediatSG message, CancellationToken cancellationToken, MessageHandlerDelegate next)
20 | {
21 | return next(message, cancellationToken);
22 | }
23 |
24 | // version 3.x
25 | public ValueTask Handle(PingMediatSG message, MessageHandlerDelegate next, CancellationToken cancellationToken)
26 | {
27 | return next(message, cancellationToken);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Benchmark/StreamRequest/StreamDispatchRCommands.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
2 | using System.Runtime.CompilerServices;
3 | using DispatchR.Requests;
4 | using DispatchR.Requests.Stream;
5 |
6 | namespace Benchmark.StreamRequest;
7 |
8 | public sealed record PingStreamDispatchR : IStreamRequest { }
9 |
10 | public sealed record PingStreamDispatchRWithOutHandler : IStreamRequest { }
11 |
12 | public sealed class PingHandlerDispatchR : IStreamRequestHandler
13 | {
14 | public async IAsyncEnumerable Handle(PingStreamDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken)
15 | {
16 | yield return 0;
17 | }
18 | }
19 |
20 | public sealed class LoggingBehaviorDispatchR : IStreamPipelineBehavior
21 | {
22 | public required IStreamRequestHandler NextPipeline { get; set; }
23 |
24 | public async IAsyncEnumerable Handle(PingStreamDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken)
25 | {
26 | await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
27 | {
28 | yield return response;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Benchmark/StreamRequest/StreamMediatRCommands.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
2 | using System.Runtime.CompilerServices;
3 | using MediatR;
4 | namespace Benchmark.StreamRequest;
5 |
6 | public sealed class PingStreamMediatR : IStreamRequest { }
7 | public sealed class PingStreamMediatRWithOutHandler : IStreamRequest { }
8 |
9 | public sealed class PingHandlerMediatR : IStreamRequestHandler
10 | {
11 | public async IAsyncEnumerable Handle(PingStreamMediatR request, [EnumeratorCancellation] CancellationToken cancellationToken)
12 | {
13 | yield return 0;
14 | }
15 | }
16 |
17 | public sealed class LoggingBehaviorMediat : IStreamPipelineBehavior
18 | {
19 | public async IAsyncEnumerable Handle(PingStreamMediatR request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken)
20 | {
21 | await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false))
22 | {
23 | yield return response;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Benchmark/StreamRequest/StreamMediatRVsDispatchRBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Order;
3 | using DispatchR;
4 | using IMediator = MediatR.IMediator;
5 |
6 | namespace Benchmark.StreamRequest;
7 |
8 | [MemoryDiagnoser]
9 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
10 | public class StreamMediatRVsDispatchBenchmark
11 | {
12 | private const int TotalStreamRequests = 5_000;
13 | private IServiceScope _serviceScopeForMediatRWithoutPipeline;
14 | private IServiceScope _serviceScopeForMediatSgWithoutPipeline;
15 | private IServiceScope _serviceScopeForDispatchRWithoutPipeline;
16 | private DispatchR.Requests.IMediator _dispatchRWithoutPipeline;
17 | private IMediator _mediatRWithoutPipeline;
18 | private Mediator.IMediator _mediatSgWithoutPipeline;
19 | private static readonly PingStreamDispatchR StaticStreamDispatchR = new();
20 | private static readonly PingStreamMediatR StaticPingStreamMediatR = new();
21 | private static readonly PingStreamMediatSg StaticPingStreamMediatSg = new();
22 | private static readonly PingStreamDispatchRWithOutHandler StaticStreamDispatchRRequestWithOutHandler = new();
23 | private static readonly PingStreamMediatRWithOutHandler StaticPingStreamMediatRWithOutHandler = new();
24 | private static readonly PingStreamMediatSgWithOutHandler StaticPingStreamMediatSgWithOutHandler = new();
25 | private static List ScopesForMediatRWithoutPipeline { get; set; } = new(TotalStreamRequests);
26 | private static List ScopesForMediatSgWithoutPipeline { get; set; } = new(TotalStreamRequests);
27 | private static List ScopesForDispatchRWithoutPipeline { get; set; } = new(TotalStreamRequests);
28 |
29 | [GlobalSetup]
30 | public void Setup()
31 | {
32 | var withoutPipelineServices = new ServiceCollection();
33 | withoutPipelineServices.AddMediatR(cfg =>
34 | {
35 | cfg.Lifetime = ServiceLifetime.Scoped;
36 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly);
37 | });
38 | withoutPipelineServices.AddMediator(opts =>
39 | {
40 | opts.ServiceLifetime = ServiceLifetime.Scoped;
41 | });
42 | withoutPipelineServices.AddDispatchR(typeof(PingStreamDispatchR).Assembly, withPipelines: false);
43 | var buildServicesWithoutPipeline = withoutPipelineServices.BuildServiceProvider();
44 | _dispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
45 | _mediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
46 | _mediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
47 | _serviceScopeForMediatRWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
48 | _serviceScopeForMediatSgWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
49 | _serviceScopeForDispatchRWithoutPipeline = buildServicesWithoutPipeline.CreateScope();
50 | ScopesForMediatRWithoutPipeline.Clear();
51 | ScopesForMediatSgWithoutPipeline.Clear();
52 | ScopesForDispatchRWithoutPipeline.Clear();
53 | Parallel.For(0, TotalStreamRequests, i =>
54 | {
55 | ScopesForMediatRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
56 | ScopesForDispatchRWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
57 | ScopesForMediatSgWithoutPipeline.Add(buildServicesWithoutPipeline.CreateScope());
58 | });
59 | }
60 |
61 | [GlobalCleanup]
62 | public void Cleanup()
63 | {
64 | _serviceScopeForMediatRWithoutPipeline.Dispose();
65 | _serviceScopeForMediatSgWithoutPipeline.Dispose();
66 | _serviceScopeForDispatchRWithoutPipeline.Dispose();
67 | _dispatchRWithoutPipeline = null!;
68 | _mediatRWithoutPipeline = null!;
69 | _mediatSgWithoutPipeline = null!;
70 | Parallel.ForEach(ScopesForMediatRWithoutPipeline, scope => scope.Dispose());
71 | Parallel.ForEach(ScopesForMediatSgWithoutPipeline, scope => scope.Dispose());
72 | Parallel.ForEach(ScopesForDispatchRWithoutPipeline, scope => scope.Dispose());
73 | }
74 |
75 | #region StreamRequest_With_ExistRequest_ExistMediator_WithOutHandler
76 |
77 | [Benchmark(Baseline = true)]
78 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
79 | {
80 | try
81 | {
82 | var last = 0;
83 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatRWithOutHandler, CancellationToken.None).ConfigureAwait(false))
84 | {
85 | last = response;
86 | }
87 |
88 | return last;
89 | }
90 | catch
91 | {
92 | return 0;
93 | }
94 | }
95 |
96 | [Benchmark]
97 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
98 | {
99 | try
100 | {
101 | var last = 0;
102 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSgWithOutHandler, CancellationToken.None).ConfigureAwait(false))
103 | {
104 | last = response;
105 | }
106 |
107 | return last;
108 | }
109 | catch
110 | {
111 | return 0;
112 | }
113 | }
114 |
115 | [Benchmark]
116 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator_WithOut_Handler()
117 | {
118 | try
119 | {
120 | var last = 0;
121 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchRRequestWithOutHandler, CancellationToken.None).ConfigureAwait(false))
122 | {
123 | last = response;
124 | }
125 |
126 | return last;
127 | }
128 | catch
129 | {
130 | return 0;
131 | }
132 | }
133 |
134 | #endregion
135 |
136 | #region StreamRequest_With_ExistRequest_ExistMediator
137 |
138 | [Benchmark]
139 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator()
140 | {
141 | var last = 0;
142 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
143 | {
144 | last = response;
145 | }
146 | return last;
147 | }
148 |
149 | [Benchmark]
150 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator()
151 | {
152 | var last = 0;
153 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
154 | {
155 | last = response;
156 | }
157 | return last;
158 | }
159 |
160 | [Benchmark]
161 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator()
162 | {
163 | var last = 0;
164 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
165 | {
166 | last = response;
167 | }
168 | return last;
169 | }
170 |
171 | #endregion
172 |
173 | #region StreamRequest_With_ExistRequest_GetMediator
174 |
175 | [Benchmark]
176 | public async Task MediatR___StreamRequest_With_ExistRequest_GetMediator()
177 | {
178 | var mediator = _serviceScopeForMediatRWithoutPipeline
179 | .ServiceProvider
180 | .GetRequiredService();;
181 |
182 | var last = 0;
183 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
184 | {
185 | last = response;
186 | }
187 | return last;
188 | }
189 |
190 | [Benchmark]
191 | public async Task MediatSG__StreamRequest_With_ExistRequest_GetMediator()
192 | {
193 | var mediator = _serviceScopeForMediatSgWithoutPipeline
194 | .ServiceProvider
195 | .GetRequiredService();
196 |
197 | var last = 0;
198 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
199 | {
200 | last = response;
201 | }
202 | return last;
203 | }
204 |
205 | [Benchmark]
206 | public async Task DispatchR_StreamRequest_With_ExistRequest_GetMediator()
207 | {
208 | var mediator = _serviceScopeForDispatchRWithoutPipeline
209 | .ServiceProvider
210 | .GetRequiredService();
211 |
212 | var last = 0;
213 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
214 | {
215 | last = response;
216 | }
217 | return last;
218 | }
219 |
220 | #endregion
221 |
222 | #region StreamRequest_With_ExistRequest_ExistMediator_Parallel
223 |
224 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
225 | public async Task MediatR___StreamRequest_With_ExistRequest_ExistMediator_Parallel()
226 | {
227 | var result = 0;
228 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
229 | {
230 | await foreach (var response in _mediatRWithoutPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
231 | {
232 | result = response;
233 | }
234 | });
235 |
236 | return result;
237 | }
238 |
239 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
240 | public async Task MediatSG__StreamRequest_With_ExistRequest_ExistMediator_Parallel()
241 | {
242 | var result = 0;
243 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
244 | {
245 | await foreach (var response in _mediatSgWithoutPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
246 | {
247 | result = response;
248 | }
249 | });
250 |
251 | return result;
252 | }
253 |
254 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
255 | public async Task DispatchR_StreamRequest_With_ExistRequest_ExistMediator_Parallel()
256 | {
257 | var result = 0;
258 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
259 | {
260 | await foreach (var response in _dispatchRWithoutPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
261 | {
262 | result = response;
263 | }
264 | });
265 |
266 | return result;
267 | }
268 |
269 | #endregion
270 |
271 | #region StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel
272 |
273 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
274 | public async Task MediatR___StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
275 | {
276 | var result = 0;
277 | await Parallel.ForEachAsync(ScopesForMediatRWithoutPipeline, async (scope, ct) =>
278 | {
279 | var mediator = scope.ServiceProvider.GetRequiredService();
280 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
281 | {
282 | result = response;
283 | }
284 | });
285 |
286 | return result;
287 | }
288 |
289 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
290 | public async Task MediatSG__StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
291 | {
292 | var result = 0;
293 | await Parallel.ForEachAsync(ScopesForMediatSgWithoutPipeline, async (scope, ct) =>
294 | {
295 | var mediator = scope.ServiceProvider.GetRequiredService();
296 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
297 | {
298 | result = response;
299 | }
300 | });
301 |
302 | return result;
303 | }
304 |
305 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
306 | public async Task DispatchR_StreamRequest_With_ExistRequest_GetMediator_ExistScopes_Parallel()
307 | {
308 | var result = 0;
309 | await Parallel.ForEachAsync(ScopesForDispatchRWithoutPipeline, async (scope, ct) =>
310 | {
311 | var mediator = scope.ServiceProvider.GetRequiredService();
312 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
313 | {
314 | result = response;
315 | }
316 | });
317 |
318 | return result;
319 | }
320 |
321 | #endregion
322 | }
--------------------------------------------------------------------------------
/src/Benchmark/StreamRequest/StreamMediatRVsDispatchRWithPipelineBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Order;
3 | using DispatchR;
4 | using Mediator;
5 | using IMediator = MediatR.IMediator;
6 |
7 | namespace Benchmark.StreamRequest;
8 |
9 | [MemoryDiagnoser]
10 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
11 | public class StreamMediatRVsDispatchWithPipelineRBenchmark
12 | {
13 | private const int TotalStreamRequests = 5_000;
14 | private IServiceScope _serviceScopeForMediatRWithPipeline;
15 | private IServiceScope _serviceScopeForMediatSgWithPipeline;
16 | private IServiceScope _serviceScopeForDispatchRWithPipeline;
17 | private DispatchR.Requests.IMediator _dispatchRWithPipeline;
18 | private IMediator _mediatRWithPipeline;
19 | private Mediator.IMediator _mediatSgWithPipeline;
20 | private static readonly PingStreamDispatchR StaticStreamDispatchR = new();
21 | private static readonly PingStreamMediatR StaticPingStreamMediatR = new();
22 | private static readonly PingStreamMediatSg StaticPingStreamMediatSg = new();
23 | private static readonly PingStreamDispatchRWithOutHandler StaticStreamDispatchRRequestWithOutHandler = new();
24 | private static readonly PingStreamMediatRWithOutHandler StaticPingStreamMediatRWithOutHandler = new();
25 | private static readonly PingStreamMediatSgWithOutHandler StaticPingStreamMediatSgWithOutHandler = new();
26 | private static List ScopesForMediatRWithPipeline { get; set; } = new(TotalStreamRequests);
27 | private static List ScopesForMediatSgWithPipeline { get; set; } = new(TotalStreamRequests);
28 | private static List ScopesForDispatchRWithPipeline { get; set; } = new(TotalStreamRequests);
29 |
30 | [GlobalSetup]
31 | public void Setup()
32 | {
33 | var withPipelineServices = new ServiceCollection();
34 |
35 | withPipelineServices.AddMediatR(cfg =>
36 | {
37 | cfg.Lifetime = ServiceLifetime.Scoped;
38 | cfg.RegisterServicesFromAssemblies(typeof(PingHandlerMediatR).Assembly);
39 | });
40 | withPipelineServices.AddScoped, LoggingBehaviorMediat>();
41 |
42 | withPipelineServices.AddMediator((MediatorOptions options) =>
43 | {
44 | options.ServiceLifetime = ServiceLifetime.Scoped;
45 | // options.PipelineBehaviors = [typeof(LoggingBehaviorMediatSG)];
46 | });
47 | withPipelineServices.AddScoped, LoggingBehaviorMediatSg>();
48 |
49 | withPipelineServices.AddDispatchR(typeof(PingStreamDispatchR).Assembly);
50 | var buildServicesWithoutPipeline = withPipelineServices.BuildServiceProvider();
51 | _dispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
52 | _mediatRWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
53 | _mediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope().ServiceProvider.GetRequiredService();
54 | _serviceScopeForMediatRWithPipeline = buildServicesWithoutPipeline.CreateScope();
55 | _serviceScopeForMediatSgWithPipeline = buildServicesWithoutPipeline.CreateScope();
56 | _serviceScopeForDispatchRWithPipeline = buildServicesWithoutPipeline.CreateScope();
57 | ScopesForMediatRWithPipeline.Clear();
58 | ScopesForMediatSgWithPipeline.Clear();
59 | ScopesForDispatchRWithPipeline.Clear();
60 | Parallel.For(0, TotalStreamRequests, i =>
61 | {
62 | ScopesForMediatRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
63 | ScopesForDispatchRWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
64 | ScopesForMediatSgWithPipeline.Add(buildServicesWithoutPipeline.CreateScope());
65 | });
66 | }
67 |
68 | [GlobalCleanup]
69 | public void Cleanup()
70 | {
71 | _serviceScopeForMediatRWithPipeline.Dispose();
72 | _serviceScopeForMediatSgWithPipeline.Dispose();
73 | _serviceScopeForDispatchRWithPipeline.Dispose();
74 | _dispatchRWithPipeline = null!;
75 | _mediatRWithPipeline = null!;
76 | _mediatSgWithPipeline = null!;
77 | Parallel.ForEach(ScopesForMediatRWithPipeline, scope => scope.Dispose());
78 | Parallel.ForEach(ScopesForMediatSgWithPipeline, scope => scope.Dispose());
79 | Parallel.ForEach(ScopesForDispatchRWithPipeline, scope => scope.Dispose());
80 | }
81 |
82 | #region StreamRequest_With_Pipeline_ExistRequest_ExistMediator_WithOutHandler
83 |
84 | [Benchmark(Baseline = true)]
85 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator_WithOut_Handler()
86 | {
87 | try
88 | {
89 | var result = 0;
90 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatRWithOutHandler, CancellationToken.None).ConfigureAwait(false))
91 | {
92 | result = response;
93 | }
94 |
95 | return result;
96 | }
97 | catch
98 | {
99 | return 0;
100 | }
101 | }
102 |
103 | [Benchmark]
104 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator_WithOut_Handler()
105 | {
106 | try
107 | {
108 | var result = 0;
109 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSgWithOutHandler, CancellationToken.None).ConfigureAwait(false))
110 | {
111 | result = response;
112 | }
113 |
114 | return result;
115 | }
116 | catch
117 | {
118 | return 0;
119 | }
120 | }
121 |
122 | [Benchmark]
123 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator_WithOut_Handler()
124 | {
125 | try
126 | {
127 | var result = 0;
128 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchRRequestWithOutHandler, CancellationToken.None).ConfigureAwait(false))
129 | {
130 | result = response;
131 | }
132 |
133 | return result;
134 | }
135 | catch
136 | {
137 | return 0;
138 | }
139 | }
140 |
141 | #endregion
142 |
143 | #region StreamRequest_ExistRequest_ExistMediator
144 |
145 | [Benchmark]
146 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator()
147 | {
148 | var result = 0;
149 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
150 | {
151 | result = response;
152 | }
153 |
154 | return result;
155 | }
156 |
157 | [Benchmark]
158 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator()
159 | {
160 | var result = 0;
161 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
162 | {
163 | result = response;
164 | }
165 |
166 | return result;
167 | }
168 |
169 | [Benchmark]
170 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator()
171 | {
172 | var result = 0;
173 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
174 | {
175 | result = response;
176 | }
177 |
178 | return result;
179 | }
180 |
181 | #endregion
182 |
183 | #region StreamRequest_ExistRequest_GetMediator
184 |
185 | [Benchmark]
186 | public async Task MediatR___StreamRequest_ExistRequest_GetMediator()
187 | {
188 | var mediator = _serviceScopeForMediatRWithPipeline
189 | .ServiceProvider
190 | .GetRequiredService();
191 |
192 | var result = 0;
193 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
194 | {
195 | result = response;
196 | }
197 |
198 | return result;
199 | }
200 |
201 | [Benchmark]
202 | public async Task MediatSG__StreamRequest_ExistRequest_GetMediator()
203 | {
204 | var mediator = _serviceScopeForMediatSgWithPipeline
205 | .ServiceProvider
206 | .GetRequiredService();
207 |
208 | var result = 0;
209 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
210 | {
211 | result = response;
212 | }
213 |
214 | return result;
215 | }
216 |
217 | [Benchmark]
218 | public async Task DispatchR_StreamRequest_ExistRequest_GetMediator()
219 | {
220 | var mediator = _serviceScopeForDispatchRWithPipeline
221 | .ServiceProvider
222 | .GetRequiredService();
223 |
224 | var result = 0;
225 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
226 | {
227 | result = response;
228 | }
229 |
230 | return result;
231 | }
232 |
233 | #endregion
234 |
235 | #region StreamRequest_ExistRequest_ExistMediator_Parallel
236 |
237 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
238 | public async Task MediatR___StreamRequest_ExistRequest_ExistMediator_Parallel()
239 | {
240 | var result = 0;
241 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
242 | {
243 | await foreach (var response in _mediatRWithPipeline.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
244 | {
245 | result = response;
246 | }
247 | });
248 |
249 | return result;
250 | }
251 |
252 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
253 | public async Task MediatSG__StreamRequest_ExistRequest_ExistMediator_Parallel()
254 | {
255 | var result = 0;
256 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
257 | {
258 | await foreach (var response in _mediatSgWithPipeline.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
259 | {
260 | result = response;
261 | }
262 | });
263 |
264 | return result;
265 | }
266 |
267 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
268 | public async Task DispatchR_StreamRequest_ExistRequest_ExistMediator_Parallel()
269 | {
270 | var result = 0;
271 | await Parallel.ForAsync(0, TotalStreamRequests, async (index, ct) =>
272 | {
273 | await foreach (var response in _dispatchRWithPipeline.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
274 | {
275 | result = response;
276 | }
277 | });
278 |
279 | return result;
280 | }
281 |
282 | #endregion
283 |
284 | #region StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel
285 |
286 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
287 | public async Task MediatR___StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
288 | {
289 | var result = 0;
290 | await Parallel.ForEachAsync(ScopesForMediatRWithPipeline, async (scope, ct) =>
291 | {
292 | var mediator = scope.ServiceProvider.GetRequiredService();
293 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatR, CancellationToken.None).ConfigureAwait(false))
294 | {
295 | result = response;
296 | }
297 | });
298 |
299 | return result;
300 | }
301 |
302 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
303 | public async Task MediatSG__StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
304 | {
305 | var result = 0;
306 | await Parallel.ForEachAsync(ScopesForMediatSgWithPipeline, async (scope, ct) =>
307 | {
308 | var mediator = scope.ServiceProvider.GetRequiredService();
309 |
310 | await foreach (var response in mediator.CreateStream(StaticPingStreamMediatSg, CancellationToken.None).ConfigureAwait(false))
311 | {
312 | result = response;
313 | }
314 | });
315 |
316 | return result;
317 | }
318 |
319 | [Benchmark(OperationsPerInvoke = TotalStreamRequests)]
320 | public async Task DispatchR_StreamRequest_ExistRequest_GetMediator_ExistScopes_Parallel()
321 | {
322 | var result = 0;
323 | await Parallel.ForEachAsync(ScopesForDispatchRWithPipeline, async (scope, ct) =>
324 | {
325 | var mediator = scope.ServiceProvider.GetRequiredService();
326 | await foreach (var response in mediator.CreateStream(StaticStreamDispatchR, CancellationToken.None).ConfigureAwait(false))
327 | {
328 | result = response;
329 | }
330 | });
331 |
332 | return result;
333 | }
334 |
335 | #endregion
336 | }
--------------------------------------------------------------------------------
/src/Benchmark/StreamRequest/StreamMediatSGCommands.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
2 | using System.Runtime.CompilerServices;
3 | using Mediator;
4 |
5 | namespace Benchmark.StreamRequest;
6 |
7 | public sealed class PingStreamMediatSg : IStreamRequest { }
8 | public sealed class PingStreamMediatSgWithOutHandler : IStreamRequest { }
9 |
10 | public sealed class PingHandlerMediatSg : IStreamRequestHandler
11 | {
12 | public async IAsyncEnumerable Handle(PingStreamMediatSg request, [EnumeratorCancellation] CancellationToken cancellationToken)
13 | {
14 | yield return 0;
15 | }
16 | }
17 |
18 | public sealed class LoggingBehaviorMediatSg : IStreamPipelineBehavior
19 | {
20 | // version 2.x
21 | public async IAsyncEnumerable Handle(PingStreamMediatSg message, [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next)
22 | {
23 | await foreach (var response in next(message, cancellationToken).ConfigureAwait(false))
24 | {
25 | yield return response;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Benchmark/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Benchmark/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/src/DispatchR/DispatchR.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | README.md
8 | DispatchR.Mediator
9 | hasanxdev
10 |
11 | Fast, zero-alloc alternative to MediatR for .NET – minimal, blazing fast, and DI-friendly.
12 |
13 | DispatchR;Mediator;MediatR
14 | https://github.com/hasanxdev/DispatchR
15 | https://github.com/hasanxdev/DispatchR
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/DispatchR/DispatchRServiceCollection.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using DispatchR.Requests;
4 | using DispatchR.Requests.Notification;
5 | using DispatchR.Requests.Send;
6 | using DispatchR.Requests.Stream;
7 | using Microsoft.Extensions.DependencyInjection;
8 |
9 | namespace DispatchR;
10 |
11 | public static class DispatchRServiceCollection
12 | {
13 | public static void AddDispatchR(this IServiceCollection services, Assembly assembly, bool withPipelines = true, bool withNotifications = true)
14 | {
15 | services.AddScoped();
16 | var requestHandlerType = typeof(IRequestHandler<,>);
17 | var pipelineBehaviorType = typeof(IPipelineBehavior<,>);
18 | var streamRequestHandlerType = typeof(IStreamRequestHandler<,>);
19 | var streamPipelineBehaviorType = typeof(IStreamPipelineBehavior<,>);
20 | var syncNotificationHandlerType = typeof(INotificationHandler<>);
21 |
22 | var allTypes = assembly.GetTypes()
23 | .Where(p =>
24 | {
25 | var interfaces = p.GetInterfaces();
26 | return interfaces.Length >= 1 &&
27 | interfaces
28 | .Where(i => i.IsGenericType)
29 | .Select(i => i.GetGenericTypeDefinition())
30 | .Any(i => new[]
31 | {
32 | requestHandlerType,
33 | pipelineBehaviorType,
34 | streamRequestHandlerType,
35 | streamPipelineBehaviorType,
36 | syncNotificationHandlerType
37 | }.Contains(i));
38 | }).ToList();
39 |
40 | if (withNotifications)
41 | {
42 | RegisterNotification(services, allTypes, syncNotificationHandlerType);
43 | }
44 |
45 | RegisterHandlers(services, allTypes, requestHandlerType, pipelineBehaviorType,
46 | streamRequestHandlerType, streamPipelineBehaviorType, withPipelines);
47 | }
48 |
49 | private static void RegisterHandlers(IServiceCollection services, List allTypes,
50 | Type requestHandlerType, Type pipelineBehaviorType, Type streamRequestHandlerType,
51 | Type streamPipelineBehaviorType, bool withPipelines)
52 | {
53 | var allHandlers = allTypes
54 | .Where(p =>
55 | {
56 | var @interface = p.GetInterfaces().First(i => i.IsGenericType);
57 | return new[] { requestHandlerType, streamRequestHandlerType }
58 | .Contains(@interface.GetGenericTypeDefinition());
59 | }).ToList();
60 |
61 | var allPipelines = allTypes
62 | .Where(p =>
63 | {
64 | var @interface = p.GetInterfaces().First(i => i.IsGenericType);
65 | return new[] { pipelineBehaviorType, streamPipelineBehaviorType }
66 | .Contains(@interface.GetGenericTypeDefinition());
67 | }).ToList();
68 |
69 | foreach (var handler in allHandlers)
70 | {
71 | object key = handler.GUID;
72 | var handlerType = requestHandlerType;
73 | var behaviorType = pipelineBehaviorType;
74 |
75 | var isStream = handler.GetInterfaces()
76 | .Any(p => p.IsGenericType && p.GetGenericTypeDefinition() == streamRequestHandlerType);
77 | if (isStream)
78 | {
79 | handlerType = streamRequestHandlerType;
80 | behaviorType = streamPipelineBehaviorType;
81 | }
82 |
83 | services.AddKeyedScoped(typeof(IRequestHandler), key, handler);
84 |
85 | var handlerInterface = handler.GetInterfaces()
86 | .First(p => p.IsGenericType && p.GetGenericTypeDefinition() == handlerType);
87 |
88 | // find pipelines
89 | if (withPipelines)
90 | {
91 | var pipelines = allPipelines
92 | .Where(p =>
93 | {
94 | var interfaces = p.GetInterfaces();
95 | return interfaces
96 | .FirstOrDefault(inter =>
97 | inter.IsGenericType &&
98 | inter.GetGenericTypeDefinition() == behaviorType)
99 | ?.GetInterfaces().First().GetGenericTypeDefinition() ==
100 | handlerInterface.GetGenericTypeDefinition();
101 | }).ToList();
102 |
103 | foreach (var pipeline in pipelines)
104 | {
105 | services.AddKeyedScoped(typeof(IRequestHandler), key, pipeline);
106 | }
107 | }
108 |
109 | services.AddScoped(handlerInterface, sp =>
110 | {
111 | var pipelinesWithHandler = Unsafe
112 | .As(sp.GetKeyedServices(key));
113 |
114 | IRequestHandler lastPipeline = pipelinesWithHandler[0];
115 | for (int i = 1; i < pipelinesWithHandler.Length; i++)
116 | {
117 | var pipeline = pipelinesWithHandler[i];
118 | pipeline.SetNext(lastPipeline);
119 | lastPipeline = pipeline;
120 | }
121 |
122 | return lastPipeline;
123 | });
124 | }
125 | }
126 |
127 | private static void RegisterNotification(IServiceCollection services, List allTypes, Type syncNotificationHandlerType)
128 | {
129 | var allNotifications = allTypes
130 | .Where(p =>
131 | {
132 | return p.GetInterfaces()
133 | .Where(i => i.IsGenericType)
134 | .Select(i => i.GetGenericTypeDefinition())
135 | .Any(i => new[]
136 | {
137 | syncNotificationHandlerType
138 | }.Contains(i));
139 | })
140 | .GroupBy(p =>
141 | {
142 | var @interface = p.GetInterfaces()
143 | .Where(i => i.IsGenericType)
144 | .First(i => new[]
145 | {
146 | syncNotificationHandlerType
147 | }.Contains(i.GetGenericTypeDefinition()));
148 | return @interface.GenericTypeArguments.First();
149 | })
150 | .ToList();
151 |
152 | foreach (var notification in allNotifications)
153 | {
154 | foreach (var types in notification.ToList())
155 | {
156 | services.AddScoped(typeof(INotificationHandler<>).MakeGenericType(notification.Key), types);
157 | }
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/src/DispatchR/Requests/IMediator.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using DispatchR.Requests.Notification;
3 | using DispatchR.Requests.Send;
4 | using DispatchR.Requests.Stream;
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace DispatchR.Requests;
8 |
9 | public interface IMediator
10 | {
11 | TResponse Send