Welcome Builders!

Native interop: Where .NET beats the competition in app development

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.

to silverlight - verb

the process of hyping a product, onboarding developer masses and then abondon it

-- Windows Developer Community

But why we need MAUI in the first place. 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?

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?

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: Native Interop.

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 rely on plugins to expose more and more of the platforms into a targeted programming language. And that is the crux.

The .NET app framework (via the orignal startup/product Xamarin, founded by legendary Miguel de Icaza) and later extensions, consists roughly of these products:

  • .NET for Android
  • .NET for iOS
  • .NET for macOS
  • .NET for Windows (via C#/WinRT)
  • MAUI (previously Xamarin Forms)

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 😀). The native APIs. The full native APIs. 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):

  • you do not need to master a programming language for each platform (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.

  • you can avoid abstractions introduced by plugins 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.

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, this single argument sorts out all the competition. 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).

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 do, but then, they completely forget to advertise this in context of MAUI.

Summary: .NET MAUI has a unique but largely overseen selling point, Native Interop.


How the Omnisharp roadmap affects .NET in Cooperations

The announcement 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.

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.

Note: See Update in Appendix U

What defines a state of the art programming language

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 "Modern Compiler Construction" 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 language server protocol developed by the VS Code (Monaco?) team basically defines what a modern programming language is:

  • A modern, modular, non-black-box compiler
  • A debugger
  • A rich out-of-the-box library and runtime
  • A LSP server written in the same programming language

.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, ...).

The core attribute of most application programming languages is developer productivity. But that is a full spectrum reaching from simple auto-completion to AI-guided auto-coding. For me the hierarchy is the following

Basic Productivity

  1. Syntax Highlighting
  2. Meaningful errors during compilation / editing
  3. Debugging (stepping, breakpoints, ...)
  4. Correct context specific auto-completion (as in IntelliSense)
  5. Code Navigation
  6. Refactoring / Code Fixes

Advanced Productivity

  1. Advanced Debugging (as in IntelliTrace, break and edit, hot reload, ...)
  2. AI / statistical guidance (as in GitHub CoPilot, IntelliCode, ...)

Super Advanced Productivity

  1. Visual Designers (remember why we bought Visual Studio in the 90s)

Everything in basic productivity is in 2022 elementary state of the art and comes for free (rhetorically up to here: free as in beer).

What makes a programming language adopted in a coorperation?

When making decisions about programming languages, we decision makers have to consider ...

hard factors

  • The Architect asks: Does the programming language fullfill the needs?
  • The R&D lead asks: Can I hire easily for this programming language? Does my team know this language?

soft factors

  • The Apple, Linux, Web, or Windows fanboy / IT admin asks: Does it run on my platform?
  • The R&D lead asks: Do I need licenses for it?
  • The careful person asks: Is it free software? Is it Open Source Software? Can I own it? Can I rely on it?

So in a free choice situation it boils down to these soft decision factors.

On platform coverage: 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.

The actual trigger for the announcement is further investment into that. That is good and everyone likes it.

On licenses: For C#/F# we could always argue that there is VS Code and Omnisharp and "everything" 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.

And the trigger for the announcement is further investment into that. That is good and everyone likes it.

However, there is the fundamental split between basic productivity and advanced productivity and our willingness to pay for it.

✅ Are we willing to pay for advanced productivity? Answer: Yes! Evidence: Many many Visual Studio and JetBrains subscription.

❌ Are we willing to pay for basic productivity? Answer: No! Evidence: Adoption of Omnisharp and Creation of Visual Studio Community.

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.

On open vs. closed 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?

❌ All of these questions flip with this announcement. There is no trust 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. The only trust establishing situation is real open source. For basic productivity the full end to end stack needs to be open source (incl. debugger and "LSP Tools Host") as in MIT/Apache/GPL licensed or viable alternatives needs to be present (as in Eclipse for Java). Advanced productivity can be always be optional from this safeguarding perspective.

A way out

This is the simple part

  1. Make the "LSP Tools Host" and the .NET debugger open source (license wise).
  2. Make both of them pluggable with Microsoft commercial plugins for the advanced productivity. I know, especially for the debugger, that this is an hard thing.
  3. Sell us a "C# (Commercial)" with advanced productivity edition next a fully free "C# (Community)" with basic productivity edition (free as in beer AND in open source license).

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.

Considering, what happenend with Pylance (and IMHO PHP language server): The above should be a playbook for any language not only .NET/C#/F#.

Appendix A: An architecture side note

  • 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#.
  • Do not break the Roslyn project and the public availability of code analyzers/fixes (and other aspects of the "Modern Compiler Construction"). It is a modern compiler and not only an AST exporter.
  • I assume the "LSP Tool Host" 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.

Appendix D: Disclaimers

  • I am not speaking for my employer
  • I am not advocating in favor of free software vs. open source software. MIT is for me, in this local context free software.

Appendix E: An ethical side note

  • 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).
  • 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, "LSP Tools Host") just because Microsoft management is not willing to tell us their roadmap and real intention.

Appendix U: Update in December 2023

A year into the new license change and the release of the C# Dev Kit, I am happy to oberserve that Microsoft seems to have listened to the community and kept the LSP Server Host open source, and released a MIT licensed "C#" plugin and a commercial "C# DevKit". 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.

We are still waiting for the debugger.


Extension Methods

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 & principles.

Note: 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.

SOLID Principles

SOLID principles are a corner stone of object oriented design.

  • S ingle Responsibility Principle (SRP): "There should never be more than one reason for a class to change." In other words, every class should have only one responsibility.
  • O pen Closed Principle (OCP): "Software entities ... should be open for extension, but closed for modification."
  • L iskov Substitution Principle (LSP): "Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it". See also design by contract.
  • I nterface Segregation Principle (ISP): "Many client-specific interfaces are better than one general-purpose interface."
  • D ependency Inversion Principle (DIP): "Depend upon abstractions, not concretions."

-- via Wikipedia

Soft Skills (of Code)

Under the world of patterns and principles there are also soft skills 😀.

  • Readability: 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.
  • Usability: 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.

The Evaluation

So are exension methods violating these principles? Are they a bad thing in software design?

Let us dissect different use cases!

  • Extending fundamental base types: Adding an extension method to a base type.

    string foo = "Hello World";
    
    var hash = foo.HashSha1();
    
    // vs. static method
    var has = Sha1.Hash(foo);
    

    ❌ Single Responsibility: There is no space for hashing in strings. .WordCount() or .IsASCII() 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.

    ❌ 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.

    ❌ 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 using, static using and global using 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.

  • Throw helpers: Simplify your parameter guards by using one-line-extension methods.

    public void Execute(string command, int count)
    {
        command.ThrowIfNullOrEmpty();
        count.ThrowIfSmallerThan(0);
        // ...
    

    ❌ Single Responsibility: See above.

    ❌ Readability: See above.

    ❌ 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.

  • Extending the capabilities: Adding new capabilities to a type.

    public interface ILogger { void Log(string text); }
    
    ILogger log = GetLogger();
    log.LogDebug();
    

    ✅ Single Responsibility Principle: Not violated. LogDebug contributes to the responsibility of ILogger.

    ✅ Open-Close Principle: ILogger is not modified (it remains closed). However, C# extension methods make it Open for extensions.

    ✅ Liskov Substitution Principle: Another implementation of ILogger (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 ILogger interface (old and new implementation) would not be influenced.

    ✅ Interface Segregation Principle: LogDebug and Log are contracted separately from each other (they come from different types), so the interface was segregated and can be modified independently.

    🕵️‍♀️ 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.

    ✅ Readability: The contributed extended method intents to extend the capability of the original type. It belongs to the type in regards of the SRP. 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.

    ✅ Usability: The extension method improves the usability by making the functionality available with Intellisense.

  • Bridging Domains: Adding capabilities to one domain by attaching another domain.

    void Configure(IServiceCollection services)
    {
        services
            .AddLogging()
            .AddMvc();
    }
    

    ✅ Single Responsibility Principle: .AddLogging() contributes to the DI Container building capability of IServiceCollection. It also has the responsbility of initializing the ILogger infrastructure withing the Logging subsystem. Therefore, .AddLogging() has no place in implementation or the interface of IServiceCollection but is an ideal candiate for extension methods (even in the same assembly and direct next to the extended type).

    Assembly: Logging Assembly: DependencyInjection IServiceCollection + AddSingleton + AddTransient + BuildServiceProvider + AddLogging

    ✅ Open-Close Principle: IServiceCollection is not modified as Logging does not contribute to its domain and there is no need to change it since consumers can only use .AddSingleton() or .AddTransient(). The interface remains closed. However, C# extension methods make it Open for extensions.

    ✅ Liskov Substitution Principle: See above.

    ✅ 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.

    Assembly: Logging Assembly: DependencyInjection Assembly: AspNetCore IServiceCollection + AddSingleton + AddTransient + BuildServiceProvider + AddLogging + AddMvc

    🕵️‍♀️ Dependency Inversion Principle: See above.

    ✅ Readability: See above.

    ✅ Usability: See above.

Summary

Not all extension method use cases are really good usages when it comes to object-oriented programming. However, there are plenty of good ones.

Note: I will continually update this article when I see proper use cases which are interesting to document.