-
Notifications
You must be signed in to change notification settings - Fork 0
/
feed.rss
686 lines (652 loc) · 67.9 KB
/
feed.rss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>@tthiery's Journal</title>
<link>http://tthiery.github.io/</link>
<description>Welcome Builders!</description>
<copyright>2023</copyright>
<pubDate>Sun, 10 Dec 2023 14:49:16 GMT</pubDate>
<lastBuildDate>Sun, 10 Dec 2023 14:49:16 GMT</lastBuildDate>
<item>
<title>Native interop: Where .NET beats the competition in app development</title>
<link>http://tthiery.github.io/posts/2023/11/20/why-dotnet-for-apps</link>
<description><p>In the recent days Visual Studio for Mac was killed of and again the fears that .NET's MAUI is silverlighted by Microsoft comes up.</p></description>
<guid>http://tthiery.github.io/posts/2023/11/20/why-dotnet-for-apps</guid>
<pubDate>Mon, 20 Nov 2023 00:00:00 GMT</pubDate>
<content:encoded><p>In the recent days Visual Studio for Mac was killed of and again the fears that .NET's MAUI is silverlighted by Microsoft comes up.</p>
<blockquote class="blockquote">
<p>to silverlight - verb</p>
<p>the process of hyping a product, onboarding developer masses and then abondon it</p>
<p>-- Windows Developer Community</p>
</blockquote>
<p>But <strong>why we need MAUI in the first place</strong>. Is not Flutter the hot thing. Is not React Native awesome. And even the old work horse Cordova has awesome perks when it comes to sharing the UI styling and the app itself with the Web. Why MAUI?</p>
<p>Popular opinion about MAUI is that you can stay in your language when you develop your full stack app. You write your service, your logic, your app in C# and also your web page .... wait ... no you do not. Blazor is awesome but no. There is JavaScript. So you are anyway polyglot. Why then not go React Native?</p>
<p>Some years ago I evaluated the market for app frameworks. React Native was hot, Flutter was not there yet, Cordova just let PhoneGap behind it. Some smaller hot ones were around. We did fancy weighted comparison charts but there was a single line item which was crucial for us and MAUI - then Xamarin - beat everyone else by a length. And this still holds true to today: <strong>Native Interop</strong>.</p>
<p>Application development frameworks are typically bringing your favourite programming language (C#, JavaScript, Java, ...), UI toolkit (HTML, Widgets, ...) or paradigm (React, ...) onto a set of platforms (iOS, Android, MacOS, Windows, Linux). The frameworks typically expose a set of functionality and <strong>rely on plugins</strong> to expose more and more of the platforms into a targeted programming language. And that is the crux.</p>
<p>The .NET app framework (via the orignal startup/product Xamarin, founded by legendary Miguel de Icaza) and later extensions, consists roughly of these products:</p>
<ul>
<li>.NET for Android</li>
<li>.NET for iOS</li>
<li>.NET for macOS</li>
<li>.NET for Windows (via C#/WinRT)</li>
<li>MAUI (previously Xamarin Forms)</li>
</ul>
<p>MAUI is a shared UI toolkit similar to React Native (not Flutter because Flutter renders its own Material Design controls). The others however, were exposing the native APIs of Android, iOS and MacOS into .NET (and a .NET runtime / compilation mode for these targets 😀). <strong>The native APIs. The full native APIs.</strong> This unfolds into very critical aspects when it comes to accessing platform functionality (programmed in language X) from the application development framework (programmed in language Y, here in .NET):</p>
<ul>
<li><p><strong>you do not need to master a programming language for each platform</strong> (Objective-C/Swift for iOS/Mac, Kotlin/Java for Android and C++/.NET for Windows) when writing plugins. This matters when you assemble your team and do not need to hire specialists and can keep your team setup simple even if you go into the depths. Basically the full stack within the app itself.</p>
</li>
<li><p>you can <strong>avoid abstractions introduced by plugins</strong> and can access the original platform SDK or third party SDK in its original behavior and to its full extend. Do not brush this of easily. When you need to capture the first three Bluetooth LE frames within 10ms after a connection establishment of Bluetooth LE and your abstraction is not allowing you to register a handler before that (looking at you WebBluetooth) then you cannot integrate the product with the abstraction / plugin.</p>
</li>
</ul>
<p>Now you may argue: But I do not need that! Or: I have plugins for everything in my framework! Well, indeed, most apps will never need this. But if you need it, like we do, <strong>this single argument sorts out all the competition</strong>. React Native, Flutter and Cordova all have their plugins written in other languages by other awesome people. Most of them will abstract the original SDKs, maybe to unify, maybe to simplify. In .NET on Android, iOS and MacOS native development for plugins is not necessary and abstraction is an optional choice (e.g. using Xamarin.Essentials).</p>
<p>Looping back to .NET MAUI. MAUI is a UI toolkit on top of that (based on native controls). Blazor Hybrid is another UI toolkit (based on web view controls, HTML/CSS) using MAUI. There is still theoretical hope that MAUI one day will render its own control to go to places where Flutter and QT go. I hope Microsoft understands its chance here. Especially when you combine it with a Visual Designer. Partially they <a href="https://devblogs.microsoft.com/dotnet/why-dotnet/#net-design-point">do</a>, but then, they completely forget to advertise this in context of MAUI.</p>
<p>Summary: .NET MAUI has a unique but largely overseen selling point, Native Interop.</p>
</content:encoded>
</item>
<item>
<title>How the Omnisharp roadmap affects .NET in Cooperations</title>
<link>http://tthiery.github.io/posts/2022/06/18/dotnet-omnisharp-roadmap</link>
<description><p>The <a href="https://github.com/OmniSharp/omnisharp-vscode/issues/5276">announcement</a> to make the LSP server host of OmniSharp closed source hits me hard. I cannot understand why the DevDiv leadership of Microsoft is so willingly and ongoingly destroying the reputation of .NET (Core). .NET Core is one of the three core pillars (VS Code, TypeScript and .NET Core) which generated respect for Microsoft in the wider programming community within the last 10 years.</p></description>
<guid>http://tthiery.github.io/posts/2022/06/18/dotnet-omnisharp-roadmap</guid>
<pubDate>Sat, 18 Jun 2022 00:00:00 GMT</pubDate>
<content:encoded><p>The <a href="https://github.com/OmniSharp/omnisharp-vscode/issues/5276">announcement</a> to make the LSP server host of OmniSharp closed source hits me hard. I cannot understand why the DevDiv leadership of Microsoft is so willingly and ongoingly destroying the reputation of .NET (Core). .NET Core is one of the three core pillars (VS Code, TypeScript and .NET Core) which generated respect for Microsoft in the wider programming community within the last 10 years.</p>
<p>I am a .NET fanboy but I have a perspective on the broader spectrum of programming languages. I am an architect, decision maker/influencer and regularly involved in questions on whether Visual Studio Subscriptions are needed. I am not in a Microsoft shop, I am not in the silicon valley and I am in a conservative polyglot business.</p>
<p><em><strong>Note</strong>: See Update in Appendix U</em></p>
<h2 id="what-defines-a-state-of-the-art-programming-language">What defines a state of the art programming language</h2>
<p>In the past, programming languages consisted of a runtime, compiler and debugger. Microsoft's own Anders Hejlsberg (of Pascal, C#, TypeScript) explained in an awesome video named &quot;<a href="https://docs.microsoft.com/en-us/shows/seth-juarez/anders-hejlsberg-on-modern-compiler-construction">Modern Compiler Construction</a>&quot; how previously considered IDE features like code completion, syntax highlighting, code analysis or code refactoring are now part of the compiler. C# (Roslyn) and especially TypeScript have been (modern day) forerunners of this. This combined with the general industry adoption of the <a href="https://github.com/microsoft/language-server-protocol">language server protocol</a> developed by the VS Code (Monaco?) team basically defines what a modern programming language is:</p>
<ul>
<li>A modern, modular, non-black-box compiler</li>
<li>A debugger</li>
<li>A rich out-of-the-box library and runtime</li>
<li>A LSP server written in the same programming language</li>
</ul>
<p>.NET is awesome in all of them: The compiler is awesome, the debugger is awesome, the base class library is awesome and the LSP server ... well ... is there. But make no mistake: The other languages have this whole portfolio as well or are on the way there (Java, JavaScript/TS, Python, Go, Rust, PHP, ...).</p>
<p>The core attribute of most application programming languages is <strong>developer productivity</strong>. But that is a full spectrum reaching from simple auto-completion to AI-guided auto-coding. For me the hierarchy is the following</p>
<p><strong>Basic Productivity</strong></p>
<ol>
<li>Syntax Highlighting</li>
<li>Meaningful errors during compilation / editing</li>
<li>Debugging (stepping, breakpoints, ...)</li>
<li>Correct context specific auto-completion (as in IntelliSense)</li>
<li>Code Navigation</li>
<li>Refactoring / Code Fixes</li>
</ol>
<p><strong>Advanced Productivity</strong></p>
<ol start="7">
<li>Advanced Debugging (as in IntelliTrace, break and edit, hot reload, ...)</li>
<li>AI / statistical guidance (as in GitHub CoPilot, IntelliCode, ...)</li>
</ol>
<p><strong>Super Advanced Productivity</strong></p>
<ol start="9">
<li>Visual Designers (remember why we bought <strong>Visual</strong> Studio in the 90s)</li>
</ol>
<p>Everything in basic productivity is in 2022 elementary state of the art and comes for free (rhetorically up to here: free as in beer).</p>
<h2 id="what-makes-a-programming-language-adopted-in-a-coorperation">What makes a programming language adopted in a coorperation?</h2>
<p>When making decisions about programming languages, we decision makers have to consider ...</p>
<p><strong>hard factors</strong></p>
<ul>
<li>The Architect asks: Does the programming language fullfill the needs?</li>
<li>The R&amp;D lead asks: Can I hire easily for this programming language? Does my team know this language?</li>
</ul>
<p><strong>soft factors</strong></p>
<ul>
<li>The Apple, Linux, Web, or Windows fanboy / IT admin asks: Does it run on my platform?</li>
<li>The R&amp;D lead asks: Do I need licenses for it?</li>
<li>The careful person asks: Is it free software? Is it Open Source Software? Can I own it? Can I rely on it?</li>
</ul>
<p>So in a free choice situation it boils down to these soft decision factors.</p>
<p>On <strong>platform coverage</strong>: The gigantic success of VS Code and .NET Core have opened up the platform coverage topic for C#/F#. .NET can freely compete with Java due to this.</p>
<p>✅ <em>The actual trigger for the announcement is further investment into that. That is good and everyone likes it.</em></p>
<p>On <strong>licenses</strong>: For C#/F# we could always argue that there is VS Code and Omnisharp and &quot;everything&quot; is MIT licensed. We decision maker buy anyway the Visual Studio licenses because of increased productivity. The same applies with Java on the Eclipse / JetBrains IntelliJ split.</p>
<p>✅ <em>And the trigger for the announcement is further investment into that. That is good and everyone likes it.</em></p>
<p>However, there is the fundamental split between <em>basic productivity</em> and <em>advanced productivity</em> and our willingness to pay for it.</p>
<p>✅ Are we willing to pay for <em>advanced productivity</em>? Answer: Yes! Evidence: Many many Visual Studio and JetBrains subscription.</p>
<p>❌ Are we willing to pay for <em>basic productivity</em>? Answer: No! Evidence: Adoption of Omnisharp and Creation of Visual Studio Community.</p>
<p>Why we do not pay for basic productivity features: because it is 2022 and there are excellent alternatives out there. Java has all the whistles and most others have at least the basic productivity features.</p>
<p>On <strong>open vs. closed</strong> source: How deep can I inspect it when something misbehaves? Can I fix it? Am I dependent and locked on a single vendor (for price hickups, discontinuations, ...)? Can I archive the package for 30 years and fix it myself? Is it secure?</p>
<p>❌ All of these questions flip with this announcement. There is no <strong>trust</strong> in big cooperations to either discontinue a product, not care about a product or extort money from us developers. We developer got burned by Apple, Google and Microsoft on every opportunity when it comes to this. <strong>The only trust establishing situation is real open source</strong>. For <em>basic productivity</em> the full end to end stack needs to be open source (incl. debugger and &quot;LSP Tools Host&quot;) as in MIT/Apache/GPL licensed or <strong>viable</strong> alternatives needs to be present (as in Eclipse for Java). <em>Advanced productivity</em> can be always be optional from this safeguarding perspective.</p>
<h2 id="a-way-out">A way out</h2>
<p>This is the simple part</p>
<ol>
<li>Make the &quot;LSP Tools Host&quot; and the .NET debugger open source (license wise).</li>
<li>Make both of them pluggable with Microsoft commercial plugins for the <em>advanced productivity</em>. I know, especially for the debugger, that this is an hard thing.</li>
<li>Sell us a &quot;C# (Commercial)&quot; with <em>advanced productivity</em> edition next a fully free &quot;C# (Community)&quot; with <em>basic productivity</em> edition (free as in beer AND in open source license).</li>
</ol>
<p>I do not see how Microsoft can loose intellectual property, thought leadership or market share to anyone else. Not on the short term and not on the long term when trust is established. We talk here communication bridges and adapters.</p>
<p>Considering, what happenend with Pylance (and IMHO PHP language server): The above should be a playbook for any language not only .NET/C#/F#.</p>
<h2 id="appendix-a-an-architecture-side-note">Appendix A: An architecture side note</h2>
<ul>
<li>There is an awesome LSP protocol client/server library as part of the OmniSharp library. No matter what, do not close source this even if it is extended by proprietary Microsoft extensions. It is a human readable protocol. The .NET community builds language servers beyond just the languages C#/F#.</li>
<li>Do not break the Roslyn project and the public availability of code analyzers/fixes (and other aspects of the &quot;Modern Compiler Construction&quot;). It is a modern compiler and not only an AST exporter.</li>
<li>I assume the &quot;LSP Tool Host&quot; is nothing else than interacting with and editor based on text via LSP/DSP and then forwarding the events into the commercial and non-commercial plugins. Why is a stable, reliable, easy, multi-provider language/debugging server such a secret. Especially when the DSP and LSP are such a thin wrapper around human-eye visible, standardized text editor features.</li>
</ul>
<h2 id="appendix-d-disclaimers">Appendix D: Disclaimers</h2>
<ul>
<li>I am not speaking for my employer</li>
<li>I am not advocating in favor of free software vs. open source software. MIT is for me, in this local context free software.</li>
</ul>
<h2 id="appendix-e-an-ethical-side-note">Appendix E: An ethical side note</h2>
<ul>
<li>There are countless contributors who have contributed to OmniSharp, Hot Reload and the .NET Foundation (as an entity). Do not screw them. Do not steal their work (intelectionally, morally, reputation or physically).</li>
<li>We developers very well accept Microsoft's need to earn money. Go Open Core with .NET. Be open about it, be smart about it (see all of above) and include the community on the road. We developer have zero tolerance for this kind of messaging (remember: we deal with a CPU who is not ambivalent in its outcome: it is either true or false) where we are told half of the relevant information. We are now in the third communication debacle within a year (.NET Foundation, Hot Reload, &quot;LSP Tools Host&quot;) just because Microsoft management is not willing to tell us their roadmap and real intention.</li>
</ul>
<h2 id="appendix-u-update-in-december-2023">Appendix U: Update in December 2023</h2>
<p>A year into the new license change and the release of the C# Dev Kit, I am happy to oberserve that <strong>Microsoft seems to have listened to the community</strong> and kept the LSP Server Host open source, and released a MIT licensed &quot;C#&quot; plugin and a commercial &quot;C# DevKit&quot;. This is really good. I am still in hope they realize that the money is with visual designers and one day we get model Apps like we did for WinForms.</p>
<p>We are <strong>still waiting for the debugger</strong>.</p>
</content:encoded>
</item>
<item>
<title>Extension Methods</title>
<link>http://tthiery.github.io/posts/2021/07/10/extension-methods</link>
<description><p>Extension Methods in C# are a crucial element of modern .NET. They are the foundation of LINQ and used everywhere. However, the are sometimes considered as a bad smell when it comes to code reviewing. In this article I want to discuss the concept of extension methods and its position in the OO patterns &amp; principles.</p></description>
<guid>http://tthiery.github.io/posts/2021/07/10/extension-methods</guid>
<pubDate>Sat, 10 Jul 2021 00:00:00 GMT</pubDate>
<content:encoded><p>Extension Methods in C# are a crucial element of modern .NET. They are the foundation of LINQ and used everywhere. However, the are sometimes considered as a bad smell when it comes to code reviewing. In this article I want to discuss the concept of extension methods and its position in the OO patterns &amp; principles.</p>
<blockquote class="blockquote">
<p><strong>Note</strong>: Extension Methods are often used to build domain specific languages (might it be a SwiftUI-alike, a mocking specification system, ...). Everything goes in this space. The target there is the newly constructed language. In this article, I discuss the integration into the regular C# programming language and not a specialization of it.</p>
</blockquote>
<h2 id="solid-principles">SOLID Principles</h2>
<p>SOLID principles are a corner stone of object oriented design.</p>
<blockquote class="blockquote">
<ul>
<li><strong>S</strong> ingle Responsibility Principle (<em>SRP</em>): &quot;There should never be more than one reason for a class to change.&quot; In other words, every class should have only one responsibility.</li>
<li><strong>O</strong> pen Closed Principle (<em>OCP</em>): &quot;Software entities ... should be open for extension, but closed for modification.&quot;</li>
<li><strong>L</strong> iskov Substitution Principle (<em>LSP</em>): &quot;Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it&quot;. See also design by contract.</li>
<li><strong>I</strong> nterface Segregation Principle (<em>ISP</em>): &quot;Many client-specific interfaces are better than one general-purpose interface.&quot;</li>
<li><strong>D</strong> ependency Inversion Principle (<em>DIP</em>): &quot;Depend upon abstractions, not concretions.&quot;</li>
</ul>
<p>-- via <a href="https://en.wikipedia.org/wiki/SOLID">Wikipedia</a></p>
</blockquote>
<h2 id="soft-skills-of-code">Soft Skills (of Code)</h2>
<p>Under the world of patterns and principles there are also soft skills 😀.</p>
<ul>
<li><strong>Readability</strong>: Code is read 100 times as often as it is created or modified. It is read in full fledged editors or in a diff tool on the command line without even syntax highlighting. Regular (as in 99% of) code has to be optimized for readability.</li>
<li><strong>Usability</strong>: When writing function with the intention of other developers using it, usability is a concern. The definition of the function as well as its integration into the workflow of the user in the IDE matters here.</li>
</ul>
<h2 id="the-evaluation">The Evaluation</h2>
<p>So are exension methods violating these principles? Are they a bad thing in software design?</p>
<p>Let us dissect different use cases!</p>
<ul>
<li><p><strong>Extending fundamental base types</strong>: Adding an extension method to a base type.</p>
<pre><code class="language-csharp">string foo = &quot;Hello World&quot;;
var hash = foo.HashSha1();
// vs. static method
var has = Sha1.Hash(foo);
</code></pre>
<p>❌ Single Responsibility: There is no space for hashing in strings. <code>.WordCount()</code> or <code>.IsASCII()</code> maybe, because the contribute to the responsibility to represent text. But Hash is a different domain. The responsibility of hashing and the responsibility of text representation does not intersect. This usage only moves the argument from the braces to the context.</p>
<p>❌ Readability: There is no gain versus the usage in traditional static methods. The extension method feels like a transformation, the static method invocation like a function call. Both are fundamentally well understood concept when reading code. Both are valid patterns in object oriented programming and functional programming. The significant negative thing the extension method does is hiding the ownership of the function.</p>
<p>❌ Usability: There is no benefit in the general usability either. Auto-Completion would be cluttered with suggestions of countless domains. Namespace (and by extend static class names) with <code>using</code>, <code>static using</code> and <code>global using</code> have a rationale to exist, which is de-cluttering the global namespace and resolving potential conflicts in naming. Extension methods are typically attached to more generic namespaces to unfold their availability.</p>
</li>
<li><p><strong>Throw helpers</strong>: Simplify your parameter guards by using one-line-extension methods.</p>
<pre><code class="language-csharp">public void Execute(string command, int count)
{
command.ThrowIfNullOrEmpty();
count.ThrowIfSmallerThan(0);
// ...
</code></pre>
<p>❌ Single Responsibility: See above.</p>
<p>❌ Readability: See above.</p>
<p>❌ Usability: See above. It is even worse here, since the contributed functions are only for a special purpose which is only used at the beginning of a function.</p>
</li>
<li><p><strong>Extending the capabilities</strong>: Adding new capabilities to a type.</p>
<pre><code class="language-csharp">public interface ILogger { void Log(string text); }
ILogger log = GetLogger();
log.LogDebug();
</code></pre>
<p>✅ Single Responsibility Principle: Not violated. LogDebug contributes to the responsibility of <code>ILogger</code>.</p>
<p>✅ Open-Close Principle: <code>ILogger</code> is not modified (it remains closed). However, C# extension methods make it Open for <strong>extensions</strong>.</p>
<p>✅ Liskov Substitution Principle: Another implementation of <code>ILogger</code> (or another derived class of a default logger) can be provided without being influenced by the extension mehtod. The extension methods, like other consumers expect an unchanged behavior. Another consumer of <code>ILogger</code> interface (old and new implementation) would not be influenced.</p>
<p>✅ Interface Segregation Principle: <code>LogDebug</code> and <code>Log</code> are contracted separately from each other (they come from different types), so the interface was segregated and can be modified independently.</p>
<p>🕵️♀️ Dependency Inversion Principle: There is no abstraction here. The extension method cannot be provided by a third party (like a DI container) and abstracted by an interface. It is a static method.</p>
<p>✅ Readability: The contributed extended method intents to extend the capability of the original type. It belongs to the type in regards of the <em>SRP</em>. Additionally - in this example and as a recommendation - the extension method naming is adjusted to the target type helping the reader to understand the purpose.</p>
<p>✅ Usability: The extension method improves the usability by making the functionality available with Intellisense.</p>
</li>
<li><p><strong>Bridging Domains</strong>: Adding capabilities to one domain by attaching another domain.</p>
<pre><code class="language-csharp">void Configure(IServiceCollection services)
{
services
.AddLogging()
.AddMvc();
}
</code></pre>
<p>✅ Single Responsibility Principle: <code>.AddLogging()</code> contributes to the DI Container building capability of <code>IServiceCollection</code>. It also has the responsbility of initializing the <code>ILogger</code> infrastructure withing the Logging subsystem. Therefore, <code>.AddLogging()</code> has no place in implementation or the interface of <code>IServiceCollection</code> but is an ideal candiate for extension methods (even in the same assembly and direct next to the extended type).</p>
<svg height="210" width="500">
<rect x="185" y="10" rx="10" width="305" height="180" style="fill:blue;opacity:0.3" />
<text x="280" y="25" style="fill:white;font-size:8pt;text-decoration:underline;opacity:0.5">Assembly: Logging</text>
<rect x="10" y="10" rx="10" width="170" height="180" style="fill:red;opacity:0.3" />
<text x="20" y="25" style="fill:white;font-size:8pt;text-decoration:underline;opacity:0.5">Assembly: DependencyInjection</text>
<circle cx="100" cy="100" r="70" style="stroke:green;stroke-width:3px;fill:transparent">
</circle>
<text x="60" y="70" style="fill:white;font-size:8pt;text-decoration:underline;">IServiceCollection</text>
<text x="60" y="85" style="fill:white;font-size:8pt;">+ AddSingleton</text>
<text x="60" y="100" style="fill:white;font-size:8pt;">+ AddTransient</text>
<text x="60" y="115" style="fill:white;font-size:8pt;">+ BuildServiceProvider</text>
<ellipse cx="150" cy="100" rx="130" ry="100" style="stroke:red;stroke-width:2px;fill:transparent" />
<text x="190" y="85" style="fill:white;font-size:8pt;">+ AddLogging</text>
<circle cx="400" cy="100" r="70" style="stroke:green;stroke-width:3px;fill:transparent">
</circle>
<line x1="260" y1="82" x2="330" y2="82" style="stroke:gray;stroke-width:2" />
</svg>
<p>✅ Open-Close Principle: <code>IServiceCollection</code> is not modified as Logging does not contribute to its domain and there is no need to change it since consumers can only use <code>.AddSingleton()</code> or <code>.AddTransient()</code>. The interface remains closed. However, C# extension methods make it Open for <strong>extensions</strong>.</p>
<p>✅ Liskov Substitution Principle: See above.</p>
<p>✅ Interface Segregation Principle: See above. Extension methods are an valid method to segregate the interface into smaller parts (here the segregated specialization into the bridged technology). Extensions methods also do not contribute to the single-inheritance limitation (which extension derives in which order and are all known) and effectively form segregated interfaces.</p>
<svg height="300" width="500">
<rect x="185" y="10" rx="10" width="120" height="180" style="fill:blue;opacity:0.3" />
<text x="200" y="25" style="fill:white;font-size:8pt;text-decoration:underline;opacity:0.5">Assembly: Logging</text>
<rect x="10" y="10" rx="10" width="170" height="180" style="fill:red;opacity:0.3" />
<text x="20" y="25" style="fill:white;font-size:8pt;text-decoration:underline;opacity:0.5">Assembly: DependencyInjection</text>
<rect x="10" y="200" rx="10" width="170" height="90" style="fill:orange;opacity:0.3" />
<text x="20" y="215" style="fill:white;font-size:8pt;text-decoration:underline;opacity:0.5">Assembly: AspNetCore</text>
<circle cx="100" cy="100" r="70" style="stroke:green;stroke-width:3px;fill:transparent">
</circle>
<text x="60" y="70" style="fill:white;font-size:8pt;text-decoration:underline;">IServiceCollection</text>
<text x="60" y="85" style="fill:white;font-size:8pt;">+ AddSingleton</text>
<text x="60" y="100" style="fill:white;font-size:8pt;">+ AddTransient</text>
<text x="60" y="115" style="fill:white;font-size:8pt;">+ BuildServiceProvider</text>
<ellipse cx="150" cy="100" rx="130" ry="100" style="stroke:red;stroke-width:2px;fill:transparent" />
<text x="190" y="85" style="fill:white;font-size:8pt;">+ AddLogging</text>
<ellipse cx="100" cy="150" rx="100" ry="130" style="stroke:red;stroke-width:2px;fill:transparent" />
<text x="60" y="235" style="fill:white;font-size:8pt;">+ AddMvc</text>
</svg>
<p>🕵️♀️ Dependency Inversion Principle: See above.</p>
<p>✅ Readability: See above.</p>
<p>✅ Usability: See above.</p>
</li>
</ul>
<h2 id="summary">Summary</h2>
<p>Not all extension method use cases are really good usages when it comes to object-oriented programming. However, there are plenty of good ones.</p>
<p><em><strong>Note</strong>: I will continually update this article when I see proper use cases which are interesting to document.</em></p>
</content:encoded>
</item>
<item>
<title>On Building a Middleware Framework</title>
<link>http://tthiery.github.io/posts/2021/06/05/on-building-middleware</link>
<description><p>I am fascinated by Middleware Stacks. Middlewares help us to customize a function invocation (typically a request handler) with additional behavior before and after the invocation of function. Popular examples are authentication, logging or exception handling middlewares. Often they handle non-functional requirements (like logging, security, ...).</p></description>
<guid>http://tthiery.github.io/posts/2021/06/05/on-building-middleware</guid>
<pubDate>Sat, 05 Jun 2021 00:00:00 GMT</pubDate>
<content:encoded><p>I am fascinated by Middleware Stacks. Middlewares help us to customize a function invocation (typically a request handler) with additional behavior before and after the invocation of function. Popular examples are authentication, logging or exception handling middlewares. Often they handle non-functional requirements (like logging, security, ...).</p>
<p>As a .NET developer I use daily the prominent middleware stack in ASP.NET Core. While beautifully abstracted and designed, there is however one big caveat: It is bound to HTTP request/response. Due to that limitation I built <a href="https://github.com/violetgrass/middleware">violetgrass/middleware</a>.</p>
<p>In this article I try to discuss the various elements of a ASP.NET Core inspired middleware stack implemented in pure .NET.</p>
<h2 id="the-delegate">The delegate</h2>
<p>A middleware is a stackable function. The function definition is called the <code>MiddlewareDelegate</code> (ASP.NET Core: <code>RequestDelegate</code>).</p>
<pre><code class="language-csharp">public delegate Task MiddlewareDelegate&lt;TContext&gt;(TContext context) where TContext : Context;
</code></pre>
<p>The function expose the following characteristics:</p>
<ul>
<li>Is is <strong>asynchronous</strong>, since most likely, some part of the stack needs to access slowlier resources (e.g. a file system).</li>
<li>It receives an invocation <strong>context</strong> (essentially the input and output of the function). (ASP.NET Core: <code>HttpContext</code>).</li>
<li>It is a call without a return value.</li>
<li>It is stateless on its own (it is a function not an interface for a class).</li>
</ul>
<p>A simple message handler can be represented in the <code>MiddlewareDelegate</code>.</p>
<pre><code class="language-csharp">async Task HandleAsync(Context context)
{
var message = GetMessageFromContext(context);
Console.WriteLine(message)
}
MiddlewareDelegate&lt;Context&gt; messageHandler = HandleAsync;
var message = GetMessage();
await messageHandler(message); // process the message.
</code></pre>
<p>A middleware however, is not only the final handler, but essentially everything in the <strong>middle</strong> between the dispatching invoker and the terminal handler.
In simple cases, this could be added programmatically.</p>
<pre><code class="language-csharp">async Task HandleAsync(Context context) { /* see above */ }
async Task LogAsync(Context context)
{
Log.Write(&quot;Before&quot;);
await HandleAsync(context);
Log.Write(&quot;After&quot;);
}
async Task CatchExceptionAsync(Context context)
{
try
{
await LogAsync(context);
}
catch { /* ... */ }
}
MiddlewareDelegate&lt;Context&gt; messageHandler = CatchExceptionAsync;
var message = GetMessage();
await messageHandler(message); // try-catch, log and process the message.
</code></pre>
<p>This method of coding exposes some issues</p>
<ul>
<li>The composition of the functions influences the actual code of the functions.</li>
<li>There is not methodology to integrate third parties or have out-of-the-box functionality.</li>
</ul>
<h2 id="the-building-of-the-middleware-stack">The building of the middleware (stack)</h2>
<p>The purpose of using a middleware framework is to enable second and third party integrations into an efficient invocation stack. This includes all attributes of regular method stacking (like controlling the code before and after the invocation and handlings exceptions).</p>
<p>In a first step, the invocation of the next wrapped function needs to be parameterized.</p>
<pre><code class="language-csharp">async Task LogAsync(MiddlewareDelegate&lt;Context&gt; next, Context context)
{
Log.Write(&quot;Before&quot;);
await next(context);
Log.Write(&quot;After&quot;);
}
</code></pre>
<p>However, that would imply that the delegate is recursive and would differ between middleware (which need <code>next</code>) and the terminal handler (which does not need <code>next</code>). Closures to rescue which can bind additional variables not represented as function parameters. Closures need to be created by another function which holds the &quot;closured&quot; scope ...</p>
<pre><code class="language-csharp">MiddlewareDelegate&lt;Context&gt; LogFactory(MiddlewareDelegate&lt;Context&gt; next)
{
return async (Context context) =&gt; // = MiddlewareDelegate&lt;Context&gt;
{
Log.Write(&quot;Before&quot;);
await next(context);
Log.Write(&quot;After&quot;);
};
}
// or in modern C# with local functions instead of lambdas.
MiddlewareDelegate&lt;Context&gt; LogFactory(MiddlewareDelegate&lt;Context&gt; next)
{
return LogAsync;
async Task LogAsync(Context context)
{
Log.Write(&quot;Before&quot;);
await next(context);
Log.Write(&quot;After&quot;);
}
}
// or if you like expression bodied member
MiddlewareDelegate&lt;Context&gt; LogFactory(MiddlewareDelegate&lt;Context&gt; next)
=&gt; async context =&gt; { Log.Write(&quot;Before&quot;); await next(context); Log.Write(&quot;After&quot;); };
// or currying and first class functions
Func&lt;MiddlewareDelegate&lt;Context&gt;, MiddlewareDelegate&lt;Context&gt;&gt; LogFactory = next =&gt; async context =&gt; { Log.Write(&quot;Before&quot;); await next(context); Log.Write(&quot;After&quot;); };
</code></pre>
<p>This function which builds the middleware is called a <strong>middleware factory</strong> (<code>Func&lt;MiddlewareDelegate&lt;TContext&gt;, MiddlewareDelegate&lt;TContext&gt;&gt;</code>). It is a function which receives the <code>next</code> element in the middleware stack to built and emits a function which represent the current middleware and all middleware later in the stack.</p>
<p>You can now write ..</p>
<pre><code class="language-csharp">var messageHandlerStep3 = HandleAsync;
var messageHandlerStep2 = LogFactory(messageHandlerStep3);
var messageHandlerStep1 = CatchExceptionFactory(messageHandlerStep2);
var message = GetMessage();
await messageHandlerStep1(message); // try-catch, log and process the message.
</code></pre>
<p>So now let us build a fancy builder infrastructure for it.</p>
<p>A <code>IMiddlewareBuilder&lt;TContext&gt;</code> (ASP.NET Core: <code>IApplicationBuilder</code>) collects a set of middleware factories.</p>
<pre><code class="language-csharp">public IMiddlewareBuilder&lt;TContext&gt; Use(Func&lt;MiddlewareDelegate&lt;TContext&gt;, MiddlewareDelegate&lt;TContext&gt;&gt; middlewareFactory)
{
_factories.Add(middlewareFactory);
return this;
}
public MiddlewareDelegate&lt;TContext&gt; Build()
{
_factories.Reverse();
MiddlewareDelegate&lt;TContext&gt; current = context =&gt; Task.CompletedTask; // safeguard
foreach (var middlewareFactory in _factories)
{
current = middlewareFactory(current);
}
return current;
}
</code></pre>
<p><em><strong>Note</strong>: Ignoring all the interface definitions and additional features beyond middleware, this above is the complete business logic of violetgrass/middleware.</em></p>
<p>The above Build method iterate each factory (in reverse order) and throw the built middleware function of the current factory as an input to the factory method of the next layer. An Build invocation might look like that ...</p>
<pre><code class="language-csharp">var messageHandler = new MiddlewareBuilder&lt;Context&gt;()
.Use(CatchExceptionFactory)
.Use(LogFactory)
.Use(next =&gt; HandleAsync) // no use of next
.Build();
var message = GetMessage();
await messageHandler(message); // try-catch, log and process the message.
</code></pre>
<h2 id="configuring-the-middleware">Configuring the Middleware</h2>
<p>With the use of C# extension methods, first and third party logic can be added to the <code>MiddlewareBuilder</code>. The builder extension methods can be influenced by parameters, Dependency Injection and other sources of information.</p>
<p>Also, helper methods can be added to simplify the creation of middleware.</p>
<pre><code class="language-csharp">// Typical third part integration
public static class MiddlewareBuilderExtensions
{
public static IMiddlewareBuilder&lt;TContext&gt; UseLog(this IMiddlewareBuilder&lt;TContext&gt; self, bool verboseLogging = false)
{
// this block is run when the builder (extension) methods are invoked (one-time).
// .. allows to perform configuration and collect metadata (e.g. via parameters or DI)
return self.Use(LogFactory);
MiddlewareDelegate&lt;Context&gt; LogFactory(MiddlewareDelegate&lt;Context&gt; next)
{
// this block is run when the middleware stack is actually build (one-time).
// .. the shape of the middleware is known at this moment
// .. has closure of configuration
return LogAsync;
async Task LogAsync(Context context)
{
// this block runs when the middleware stack is invoked (each-request)
// .. has closure of configuration
// .. has closure of shape
// as a middleware it encapsulates the next from the building time
if (verboseLogging) { Log.Write(&quot;Before&quot;); }
await next(context);
if (verboseLogging) { Log.Write(&quot;After&quot;); }
}
}
}
}
// simple helper functions
public static partial class IMiddlewareBuilderExtensions
{
public static IMiddlewareBuilder&lt;TContext&gt; Use&lt;TContext&gt;(this IMiddlewareBuilder&lt;TContext&gt; self, MiddlewareDelegate&lt;TContext&gt; middleware) where TContext : Context
=&gt; self.Use(next =&gt; async context =&gt; { await middleware(context); await next(context); });
}
var messageHandler = new MiddlewareBuilder&lt;Context&gt;()
.UseExceptionHandling()
.UseLog(verbose: true)
.Use(HandleAsync)
.Build();
var message = GetMessage();
await messageHandler(message); // try-catch, log and process the message.
</code></pre>
<h2 id="conclusions">Conclusions</h2>
<p>This article hopefully gave an introduction into how the <a href="https://github.com/violetgrass/middleware">violetgrass/middleware</a> and ASP.NET Core middleware stack work. While sometimes it looks over-engineered, it is actually quite simple and a powerful extensibility framework.</p>
<p>There is more to typically middleware scenarios like dispatching and endpoint routing, however, this is material for other articles.</p>
</content:encoded>
</item>
<item>
<title>LEGO Powered UP - Motor Modes POS and APOS</title>
<link>http://tthiery.github.io/posts/2020/08/01/lego-powered-up-motor-modes-pos-and-apos</link>
<description><p>The LEGO Control+ motors - Technic XLarge Linear Motor and Technic Large Linear Motor - have internally the modes <code>POS</code> (reporting a position) and <code>APOS</code> (reporting an absolute position). Further does the LEGO Wireless Protocol specify methods <a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfordegrees-degrees-speed-maxpower-endstate-useprofile-0x0b"><code>StartSpeedForDegrees</code></a> and <a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-gotoabsoluteposition-abspos-speed-maxpower-endstate-useprofile-0x0d"><code>GotoAbsolutePosition</code></a>.</p></description>
<guid>http://tthiery.github.io/posts/2020/08/01/lego-powered-up-motor-modes-pos-and-apos</guid>
<pubDate>Sat, 01 Aug 2020 00:00:00 GMT</pubDate>
<content:encoded><p>The LEGO Control+ motors - Technic XLarge Linear Motor and Technic Large Linear Motor - have internally the modes <code>POS</code> (reporting a position) and <code>APOS</code> (reporting an absolute position). Further does the LEGO Wireless Protocol specify methods <a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfordegrees-degrees-speed-maxpower-endstate-useprofile-0x0b"><code>StartSpeedForDegrees</code></a> and <a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-gotoabsoluteposition-abspos-speed-maxpower-endstate-useprofile-0x0d"><code>GotoAbsolutePosition</code></a>.</p>
<p>Strange thing: When using <code>GotoAbsolutePosition</code> does not work as expected and goes to strange positions and not the expected absolute physical position!</p>
<p><strong>TL;DR</strong>: <code>GotoAbsolutePosition</code> does not orient itself on the <strong>physical</strong> <code>APOS</code>. It is aligned with <strong>virtual</strong> <code>POS</code>.</p>
<p>And to put it in context, a memorable quote:</p>
<blockquote class="blockquote">
<p>There are only two hard things in Computer Science: cache invalidation and naming things.</p>
<p>-- Phil Karlton</p>
</blockquote>
<h3 id="the-modes">The Modes</h3>
<p>Let us start with the simple mode: <code>APOS</code> is the absolute position in degrees measured from a zero point. It goes from 0, 1 .. 179, 180 then to -180, -179, .., -1, 0. Simple, straight forward. For the current Control+ motors the zero point is physically not marked.</p>
<p><code>POS</code> on the other hand is more complicated. The value 0 is aligned with the position of the motor was when the device was activated (most likely: put under power). From that moment on, it counts the degrees moved away from that position. And that number can get quite high (it is a <code>Int32</code>) and exceeds the traditional 0-360 degree range.</p>
<p>An example</p>
<ol>
<li>Motor is put under power: <code>POS: 0</code></li>
<li>Motor is moved by 30 degrees: <code>POS: 30</code></li>
<li>Motor is moved by -70 degrees: <code>POS: -40</code></li>
<li>Motor is further moved by two full turns (aka. -720 degrees) : <code>POS: -760</code></li>
</ol>
<h3 id="output-commands">Output Commands</h3>
<p><a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfordegrees-degrees-speed-maxpower-endstate-useprofile-0x0b"><code>StartSpeedForDegrees</code></a> moves the motor by the amount of degrees <strong>relative</strong> to the current position. If the motor is at <code>POS: 20</code> and the command is invoked with 5 degrees the motor is afterwards in <code>POS: 25</code>.</p>
<p><a href="https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-gotoabsoluteposition-abspos-speed-maxpower-endstate-useprofile-0x0d"><code>GotoAbsolutePosition</code></a> moves the motor to a given <strong>absolute</strong> position <strong>within the range of <code>POS</code></strong>. An example: If the position is <code>POS: -760</code> and command is invoked with -40 the motor will make <em><strong>two full turns</strong></em> and is <em><strong>not oriented</strong></em> 40 degrees of the physical zero but -40 degrees of the initial position of the motor. Consequently it takes an <code>Int32</code> as a parameter.</p>
<p>These <strong>two unexpected behaviors</strong> are traps when programming against the LEGO Wireless Protocol / Powered UP. They are a horrible user experience in regards to their naming (developer perspective). <strong>However, they are the correct interface and the right thing to do</strong>.</p>
<p><em><strong>Note</strong>: This reflection is based on the Technic Control+ L/XL Linear Motors. There might be a different handling with different motors. I will update the post if I become aware of a difference.</em></p>
<h3 id="adjust-the-mental-model">Adjust the mental model</h3>
<ol>
<li>For most use cases, an (physical) absolute position (<code>APOS</code>) is useless. A Technic axle can be connected in 3 wrong offsets compared to the pyhsical zero point of the motor (remember: unmarked). Even more interesting for gears.</li>
<li><code>GotoAbsolutePosition</code> should be (mentally and in SDKs) named <code>GotoPosition</code> to better reflect the fact that it is aligned with the <code>POS</code> range and not the <code>APOS</code> range.
<ul>
<li>The two full turns can then easily be explained as the amount of degrees as a delta between the current pos and the targeted position the motor should go-to.</li>
<li>The 40 degrees of the original motor position are also now okay, because that aligns with the understanding of <code>POS</code>.</li>
</ul>
</li>
</ol>
<p><em><strong>Note</strong>: As an alternative: Maybe <code>APOS</code> is wrongly named 😀. Or maybe we just need a proper documentation.</em></p>
<h3 id="practical-usage-reset-zero">Practical Usage: Reset Zero</h3>
<p>Assume a Technic Model with a steering. Magically it was calibrated to the center of your steer. The motor is at <code>POS: 254</code>.</p>
<p>The question here is: What use does <code>GotoAbsolutePosition</code> bring? Well, there is the method <code>PresetEncoder</code>. It resets <code>POS</code> to zero. Applied here, <code>GotoAbsolutePosition</code> can now comfortable control the steering around the middle position (0) without doing complicated math otherwise needed when using <code>StartSpeedForDegrees</code>.</p>
<h3 id="conclusions">Conclusions</h3>
<p>Naming is hard. Documentation is sometimes a good idea.</p>
<h3 id="appendix-users-of-sharpbrick.poweredup">Appendix: Users of SharpBrick.PoweredUp</h3>
<p>The mentioned modes and commands are exposed using the following methods:</p>
<ul>
<li><code>POS</code> =&gt; <code>TachoMotor.PositionObservable</code></li>
<li><code>APOS</code> =&gt; <code>AbsoluteMotor.AbsolutePositionObservable</code></li>
<li><code>StartSpeedForDegrees</code> =&gt; <code>TachoMotor.StartSpeedForDegreesAsync</code></li>
<li><code>GotoAbsolutePosition</code> =&gt; <code>AbsoluteMotor.GotoAbsolutePositionAsync</code>.</li>
<li><code>PresetEncoder</code> =&gt; <code>TachoMotor.SetZeroAsync</code>.</li>
</ul>
<p>There might be a change coming which <a href="https://github.com/sharpbrick/powered-up/issues/74">renames</a> and <a href="https://github.com/sharpbrick/powered-up/issues/75">moves</a> <code>GotoAbsolutePosition</code>.</p>
</content:encoded>
</item>
<item>
<title>xUnit Theories with Type Parameters</title>
<link>http://tthiery.github.io/posts/2020/07/01/xunit-with-generic-paramter</link>
<description><p>When unit testing generic protocols like the <a href="https://lego.github.io/lego-ble-wireless-protocol-docs">Lego Wireless Protocol</a> input values might be lead to different expected values. Sometimes, however, not only the value changes but also the data types of what to expect. xUnit's <code>Assert.Equal(expected, actual)</code> methods has overloads for countless basic data types like <code>short</code>, <code>double</code>, <code>bool</code> and elementary support for enums. To activate these, the expected argument would need to be parameterized with the right data type.</p></description>
<guid>http://tthiery.github.io/posts/2020/07/01/xunit-with-generic-paramter</guid>
<pubDate>Wed, 01 Jul 2020 00:00:00 GMT</pubDate>
<content:encoded><p>When unit testing generic protocols like the <a href="https://lego.github.io/lego-ble-wireless-protocol-docs">Lego Wireless Protocol</a> input values might be lead to different expected values. Sometimes, however, not only the value changes but also the data types of what to expect. xUnit's <code>Assert.Equal(expected, actual)</code> methods has overloads for countless basic data types like <code>short</code>, <code>double</code>, <code>bool</code> and elementary support for enums. To activate these, the expected argument would need to be parameterized with the right data type.</p>
<p>While this works quite nice when using fixed unit tests where the actual/expected data type is part of the method, for xUnit <strong>theories</strong> it need some more infrastructure in form of adding a type parameter to the unit test method.</p>
<p>Here an extract from the unit testing from the <a href="https://github.com/sharpbrick/powered-up">sharpbrick/powered-up</a> library.</p>
<pre><code class="language-csharp">[Theory]
[InlineData(&quot;06-00-01-02-06-00&quot;, HubProperty.Button, HubPropertyOperation.Update, false)]
[InlineData(&quot;06-00-01-02-06-01&quot;, HubProperty.Button, HubPropertyOperation.Update, true)]
[InlineData(&quot;06-00-01-05-06-61&quot;, HubProperty.Rssi, HubPropertyOperation.Update, (sbyte)97)]
[InlineData(&quot;06-00-01-06-06-64&quot;, HubProperty.BatteryVoltage, HubPropertyOperation.Update, (byte)100)]
[InlineData(&quot;06-00-01-07-06-00&quot;, HubProperty.BatteryType, HubPropertyOperation.Update, BatteryType.Normal)]
[InlineData(&quot;06-00-01-07-06-01&quot;, HubProperty.BatteryType, HubPropertyOperation.Update, BatteryType.RechargeableBlock)]
[InlineData(&quot;06-00-01-0B-06-80&quot;, HubProperty.SystemTypeId, HubPropertyOperation.Update, SystemType.LegoTechnic_MediumHub)]
[InlineData(&quot;06-00-01-0C-06-00&quot;, HubProperty.HardwareNetworkId, HubPropertyOperation.Update, (byte)0)]
public void HubPropertiesEncoder_Decode_UpdateUpstream&lt;T&gt;(string messageAsString, HubProperty expectedProperty, HubPropertyOperation expectedPropertyOperation, T payload)
{
// arrange
var data = BytesStringUtil.StringToData(messageAsString).AsSpan().Slice(3);
// act
var message = new HubPropertiesEncoder().Decode(0x00, data) as HubPropertyMessage&lt;T&gt;;
// assert
Assert.Equal(expectedProperty, message.Property);
Assert.Equal(expectedPropertyOperation, message.Operation);
Assert.Equal(payload, message.Payload);
}
</code></pre>
<p>The <code>payload</code> parameter has a data type bound to a generic. When invoking the <code>[Theory]</code> xUnit will actually instanciate the function with the suitable type parameter. As above, sometimes the <code>[InlineData(...)]</code> entry needs to be hinted (e.g. <code>(sbyte)97</code>), when C# defaults might lead to a wrong data type (e.g. <code>97</code> is of data type <code>int</code>).</p>
<p>When the <code>Attribute</code>s of C# do not allow to instantiate a data type (typically classes), a adapter shim might help:</p>
<pre><code class="language-csharp">[Theory]
[InlineData(&quot;09-00-01-03-06-00-00-00-11&quot;, HubProperty.FwVersion, HubPropertyOperation.Update, &quot;1.1.0.0&quot;)]
[InlineData(&quot;09-00-01-04-06-00-00-00-07&quot;, HubProperty.HwVersion, HubPropertyOperation.Update, &quot;0.7.0.0&quot;)]
[InlineData(&quot;07-00-01-0A-06-00-03&quot;, HubProperty.LegoWirelessProtocolVersion, HubPropertyOperation.Update, &quot;3.0&quot;)]
public void HubPropertiesEncoder_Decode_UpdateUpstream_VersionShim(string messageAsString, HubProperty expectedProperty, HubPropertyOperation expectedPropertyOperation, string payload)
=&gt; HubPropertiesEncoder_Decode_UpdateUpstream(messageAsString, expectedProperty, expectedPropertyOperation, new Version(payload));
</code></pre>
<p><em><strong>Disclaimer:</strong> I google. This feature was surely somewhere mentioned before. I do not reference the material I googled, because I lost the link.</em></p>
</content:encoded>
</item>
<item>
<title>ReactiveX Middlewares</title>
<link>http://tthiery.github.io/posts/2020/06/12/reactivex-middlewares</link>
<description><p>While implementing <a href="https://github.com/sharpbrick/powered-up">sharpbrick/powered-up</a> an interesting problem came up to solve: How to handle the incoming messages of the Bluetooth Low Energy based communication protocol. On top of the Bluetooth abstraction there is a simple callback which would receive roughly 50 different message types which would need dispatching to several locations, some locations would even spawn up dynamically over time. I needed an infrastructure! I needed a middleware 😀.</p></description>
<guid>http://tthiery.github.io/posts/2020/06/12/reactivex-middlewares</guid>
<pubDate>Fri, 12 Jun 2020 00:00:00 GMT</pubDate>
<content:encoded><p>While implementing <a href="https://github.com/sharpbrick/powered-up">sharpbrick/powered-up</a> an interesting problem came up to solve: How to handle the incoming messages of the Bluetooth Low Energy based communication protocol. On top of the Bluetooth abstraction there is a simple callback which would receive roughly 50 different message types which would need dispatching to several locations, some locations would even spawn up dynamically over time. I needed an infrastructure! I needed a middleware 😀.</p>
<h2 id="possible-solutions">Possible Solutions</h2>
<p>Within the tighter .NET ecosystem (a.k.a. everything what Microsoft and close friends throw at us) there are the following libraries for push based messaging:</p>
<ul>
<li><strong><code>System.Reactive</code></strong>: The .NET implementation part (and godfather) of the ReactiveX project (e.g. JavaScript: <code>rxjs</code>). The library is push-based, threading/buffering is optional, allow optional subscription, supports LINQ, allow building dynamic pipelines but is heap based.</li>
<li>ASP.NET Core <strong>middleware</strong> stack. Unfortunately, in the current state bound to HTTP. Standalone implementations like <a href="https://github.com/violetgrass/middleware">violetgrass/middleware</a> (disclaimer: another side project) make the basic concept available to other server/dispatcher applications.</li>
<li><code>System.Threading.Channels</code>: A part of the ASP.NET Core stack, focused on channeling data between separate producer and consumer threads. The library is push- and pull-based, buffering is including, requires active consumers and does not support LINQ.</li>
</ul>
<h2 id="middlewares">Middlewares</h2>
<p>Ignoring <code>System.Threading.Channels</code> for now, both <code>System.Reactive</code> and ASP.NET Core compiled middlewares are basically methods to build a series of functions between the place of source and an ultimate target</p>
<p>A compiled middleware is build upfront and is optimized on efficiency</p>
<pre><code class="language-csharp">public class QueueMessageContext : Context
{
public Message Message { get; set; }
}
var stack = new MiddlewareBuilder&lt;QueueMessageContext&gt;()
// middleware step
.Use(async (context, next) =&gt; {
// do something with the context.Message
await next(context);
})
// middleware step
.Use(async (context, next) =&gt; {
// do something with the context.Message
await next(context);
})
// final handling of message
.Use(async (context, next) =&gt; {
// do something with the context.Message
})
.Build();
// invoke
var x = new QueueMessageContext() { Message = msg, };
await stack(x); // the whole stack is essentially a stacked function delegate. no magic anymore. no overhead despite millions of requests.
</code></pre>
<p>Using <code>System.Reactive</code> middlewares can be dynamically constructed</p>
<pre><code class="language-csharp">var source = new Subject&lt;Message&gt;();
var firstPart = source
.Select(msg =&gt; { /* do something */ return msg; }); // middleware step
// somewhere else (e.g. on demand)
var d = firstPart
.Select(msg =&gt; { /* do something */ return msg; }) // middleware step
.Subscribe(msg =&gt; /* do something */); // final handling of message
// invoke
source.next(msg); // subscriptions are checked, data is boxed, heap is allocated, ...
</code></pre>
<p>While a compiled middleware has the benefits of performance, a reactive stream can be dynamically composed, filtered and combined (like known from LINQ). It also allows multiple subscriptions which an ASP.NET Core middleware does not. However, there is also a disadvantage: It does not support async/await and will block the producer (exept explictely configured with <code>.ObserveOn</code>). It does not naturally fit to the common <code>async/await</code> programming pattern in C#.</p>
<p>Within <a href="https://github.com/sharpbrick/powered-up">sharpbrick/powered-up</a> there is a critical requirement to dynamically compose pipelines of the incoming upstream messages to models representing PoweredUP hubs and devices.</p>
<blockquote class="blockquote">
<p><code>System.Reactive</code> is therefore the right choice to use within the library. The library behaves similar to a user interface (PoweredUP devices &lt;-&gt; UI controls) with external influences. In the world of UI progamming, the reactive programming model is used by all currently popular frameworks (React (Native), Angular, ...).</p>
</blockquote>
<p>For consumers of the library however, the <code>SharpBrick.PoweredUp</code> library exposes an async/await based interface for invocations. Internally, the library converts the subscriptions to the reactive stream into an <em>awaiter</em> using <code>.GetAwaiter()</code> as seen below in a request/response matching algorithm:</p>
<pre><code class="language-csharp">public static async Task&lt;TResultMessage&gt; SendMessageReceiveResultAsync&lt;TResultMessage&gt;(this IPoweredUpProtocol self, PoweredUpMessage requestMessage, Func&lt;TResultMessage, bool&gt; filter = default)
{
// UpstreamMessages is an IObservable
var awaitable = self.UpstreamMessages
.OfType&lt;TResultMessage&gt;()
.Where(responseMessage =&gt; filter == null || filter(responseMessage))
.FirstAsync() // only the first message observed is forwarded
.GetAwaiter(); // make sure the subscription is present at the moment the message is sent.
await self.SendMessageAsync(requestMessage);
var response = await awaitable; // subscription is discarded
return response;
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>When building an application or a library with many variable exit points - may it be UI controls or other model endpoints - for an incoming data stream, using <code>System.Reactive</code> is the right choice.</p>
</content:encoded>
</item>
<item>
<title>Using PlatformIO and Git</title>
<link>http://tthiery.github.io/posts/2020/05/21/platformio-with-git</link>
<description><p>When working on my private PlatformIO project for a watertank sensor, I repeatingly struggled with how to manage the source code. PlatformIO is unfortunately not very clear explaining this topic.</p></description>
<guid>http://tthiery.github.io/posts/2020/05/21/platformio-with-git</guid>
<pubDate>Thu, 21 May 2020 00:00:00 GMT</pubDate>
<content:encoded><p>When working on my private PlatformIO project for a watertank sensor, I repeatingly struggled with how to manage the source code. PlatformIO is unfortunately not very clear explaining this topic.</p>
<p>Simplified, the <code>platform.ini</code> contains references to the utilized SDKs and library dependencies. The only PlatformIO artifact are the <code>platform.ini</code> and the entry in the <code>extensions.json</code>.</p>
<p><code>.gitignore</code> however, will have to contain some entries to avoid commiting the cached PlatformIO is using in form of <code>.pio</code>.</p>
<h2 id="file-to-add-to-source-control">File to add to source control</h2>
<ul>
<li>.vscode
<ul>
<li>extensions.json</li>
</ul>
</li>
<li>src/* (your source code)</li>
<li>headers/* (your header)</li>
<li>platform.ini</li>
</ul>
<h2 id="files-in.gitignore">Files in <code>.gitignore</code></h2>
<ul>
<li>.pio (excluding all the cached libraries and board files)</li>
<li>.vscode/c_cpp_properties.json (cache over all C artifacts)</li>
<li>.vscode/launch.json (hardcoded to local directory by default)</li>
</ul>
</content:encoded>
</item>
<item>
<title>First Post</title>
<link>http://tthiery.github.io/posts/2019/01/23/first-post</link>
<description><p>This is my first post!</p></description>
<guid>http://tthiery.github.io/posts/2019/01/23/first-post</guid>
<pubDate>Wed, 23 Jan 2019 00:00:00 GMT</pubDate>
<content:encoded><p>This is my first post!</p>
<script src="https://gist.github.com/tthiery/2426b191135b4403c5f305727366cecf.js?file=Bar.cs"></script>
</content:encoded>
</item>
</channel>
</rss>