Dotting the Net

A few months ago I spent a little while looking at 5, but now that 6 is out (just this week, after a little ruckus concerning “hot reload”), I decided to have another go at it. Which, if you know me at all, is kind of weird.

Despite having been at Microsoft for now, I should say I don’t really like . Having spent way too much time doing , the entire object-oriented, dependency injection driven, weird abstraction overhead approach to enterprise software is something I have actively come to consider as “make work” stacked on acres of boilerplate code, and one of the main reasons I avoided “enterprise” stacks after too many rounds of broken JDKs.

always felt too much like for comfort, and although I looked at Mono in the early days (and was quite curious about Xamarin in the mobile days), current “best practices” for development actually keep me from understanding what the code is doing (what the control flow looks like, where it’s spending the most time and, crucially, why it was designed in that way).

And with software architecture and lifecycles changing so fast that most of the alleged long-term benefits of those ivory tower, heavy OOP approaches became irrelevant over time, well, I just lost interest.

And since I’ve gone down the LISP/ road, even the OOP approach seems stale. For me, ’s take on interfaces is vastly preferable (except for weird syntax and temporary lack of generics), and I quite like the comparative sparseness of and (except, again, their weird syntax) when compared to either or .

That said, I do have a soft spot for (seeing as I dabbled in OCaml for a bit) and am immersed up to my eyeballs in Microsoft toolchains, so dealing with .NET is pretty much inescapable, even for a UNIX guy.

Which .NET, Then?

This was, obviously, the critical question for a good while. Another reason why I (and my erstwhile peers) shunned for years was that it was a Windows-centric, constantly moving target with a “choice” of runtimes (i.e., a messy landscape), each of which with its own idiosyncrasies.

And yet, the ecosystem became massive. Even if (like me) you strongly dislike nuget and had nightmare experiences with NewtonSoft.Json and $-prefixed keys in legacy schemas, is out there and actually makes sense to use in various scenarios I have an interest in.

For instance, it became the language for Unity development and, again, Xamarin fixed most idiosyncrasies for mobile development (and many people I still keep track of in the iOS and Android space swear by it), so I keep stumbling into and the broader ecosystem.

Going Hard Core

Disregarding my gut feelings and dislikes, the real issues for me have been portability and long term support. Core seems to be set to fix those for everyone else, but a few months ago it wasn’t quite all there yet.

About May or so I found out that Core 5.0 could build standalone binaries for multiple architectures thanks to a combination of AOT and clang, so I decided to take a look.

Binaries were a bit on the large side (a “Hello World” build for linux-x64 yielded 34MB for C# and 50MB for F#), but could be trimmed down using -p:PublishTrimmed=true, which was encouraging.

Fast-forward until this weekend, and repeating that exercise for .NET 6 yielded similar results:

Runtime Language SingleFile Trimmed
.NET 5 C# 34MB 9MB
.NET 5 F# 50MB 13MB
.NET 6 C# 63MB 12MB
.NET 6 F# 67MB 17MB

The differences are likely due to different base sets of assemblies and the alpha state of the AOT compiler I tried at the time, but they’re pretty much inconsequential these days–I’m not too worried about file sizes at all, really.

I’m much more interested in doing cross-platform builds, which has (so far) worked perfectly and is something the toolchain does by design.

For the record, this is how you can add the experimental AOT compiler to nuget.config (freshly updated for .NET 6):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
  </packageSources>
</configuration>

And this is the Makefile I used to set these up and build them:

bootstrap:
        dotnet new console

bootstrap-fsharp:
        dotnet new console -lang f#

clean:
        rm -rf bin obj

watch:
    dotnet watch

release-mac-m1:
    dotnet publish -p:PublishSingleFile=true -r osx.11.0-arm64 -c Release

release-mac-intel:
    dotnet publish -p:PublishSingleFile=true -r osx.11.0-x64 -c Release

publish-linux:
        dotnet publish -p:PublishSingleFile=true -r linux-x64 -c Release --self-contained

publish-linux-trimmed:
        dotnet publish -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 -c Release --self-contained

Update: I’ve since , and depending on when you read this you might need to use preview versions of a couple of libraries if you want to develop on arm64. Right now that means adding these two stanzas to your .csproj:

    <PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.127" />
    <PackageReference Include="SkiaSharp" Version="2.88.0-preview.127" />

Fiddling about with a few synthetic benchmarks didn’t reveal anything interesting (I’m testing these remotely on a puny little Celeron with only 4GB RAM running Ubuntu 20.04, and speed is relative in that setup), but I don’t think there will be massive performance differences in regular, I/O-bound apps.

The big difference for me is that I don’t have a gaggle of .dll files to deploy with each app, and a slimmer footprint overall.

So for server-side stuff, this looks promising (barring my dislike of ), if only due to the massive library and tooling ecosystem.

Visual Aids

While I was playing around, I decided to see if .NET could scratch one of my long-time itches: Native-looking, non-scripting-based, cross-platform GUI apps.

Yes, I know this is the web era. I don’t care, there are many situations where I prefer to have a nice, simple local app to do stuff for me with data and files on my local drive, and I have long yearned for decent ways to build native apps that run on my Mac–and elsewhere, sometimes.

is great, but it has a lot of shortcomings where it regards building desktop apps, and my ongoing poking at has yielded a few toys, but nothing that has a decent look and feel.

Enter Avalonia. Yes, I understand there is a lot of noise about MAUI, but it’s not all there yet, and it might take a couple of years. Oh, and I am not keen on using XAML either.

But XAML is not fundamentally different from QML (I’ve been looking at Qt for a long time, and PyQt/Pyside are pretty decent until you start wading in boilerplate), and having something that demonstrably works, has loads of third-party support and a decent installed base (plus access to the entire ecosystem) seems pretty neat, and Avalonia ticks nearly all the boxes.

The only thing I’m not too sold on (yet) is how well it mimics native controls using Skia, but most of the available software I’ve come across using Avalonia (like Lunacy) is very impressive, and it took no time at all to get a Hello World app going on WSL:

a little test
A hello world app running on WSL, rendered on my Windows desktop

Avalonia can be AOT compiled too, and repeating the simple test I did for the console app yields equally acceptable results:

Runtime Language SingleFile Trimmed
.NET 6 C# 77MB 31MB
.NET 6 F# 80MB 34MB

So I grudgingly agree .NET is a lot more interesting to me these days than it was a decade ago.

Tooling

Getting things done in a terminal works fine (I’m very much a CLI and vim developer, and every year I install a new VS edition only to toss it away a few months later unused), and that’s OK for back-end stuff.

Right now, though, there aren’t that many tools to do GUI development on a Mac, which is a shame, but a logical consequence of most of .NET’s enterprise IDE push. There are third party alternatives like Rider (which is by all acounts superb but not necessarily something I need), but nothing that stands out to me as a complement.

But I’m 100% sure I really don’t want to use the full-blown Visual Studio 2022 IDE, although there are plenty of extensions (there is just one for Avalonia, though1).

Conclusion

I’m likely to use for something soon. Realistically, I haven’t the faintest idea of when I’ll have the time to wrap my brain around either C# or F# to the degree required (and sorely wish on the CLR was actually usable), but it’s become a when rather than an if.

The real problem, as always, is going to find that when amidst all the other stuff I’m supposed to do.


  1. It bears noting that there are apparently no up-to-date visual XAML editors for macOS on any free IDE, and that Avalon Studio doesn’t seem to have any working Mac builds out there either. ↩︎

This page is referenced in: