.NET has been taking huge leaps and bounds in the last few years, and not everyone is aware of it! We’re now able to build small fast single-file .NET applications; this is arguably Go’s biggest strength, and now you can do it in C# and F# too.
Meanwhile, the .NET community has been making excellent libraries to make console applications slick, polished, and easy to write.
The conditions seem right for a .NET renaissance of sorts; all the pieces are in place for us to build great CLI-first software. Here are some useful+easy things you can do in .NET today:
Publish Small Zero-Dependency Executables
In .NET 6 it’s easy to build your application as a single file with no external dependencies (even the .NET runtime!). Applications that include their own runtime are called self-contained. Self-contained apps can be trimmed to remove unused code; trimming reduces a Hello World application from about 60MB to 12MB.
Trimming is well-supported by nearly the entire standard library. Ecosystem support varies; if a library makes heavy use of reflection, C++/CLI, or COM interop then it might not work with trimming yet.
The easiest way to build a trimmed self-contained single-file executable is dotnet publish
with the args --self-contained=true -p:PublishSingleFile=true -p:PublishTrimmed=true
.
dotnet-releaser
makes publishing your app a breeze; it can build for multiple platforms+architectures, then publish the resulting artifacts in a GitHub release.
Console UI
Spectre.Console
is a great replacement for Console.WriteLine()
and friends. Coloring text with Spectre is as simple as:
AnsiConsole.MarkupLine("[blue]Hello[/] [yellow]World![/]");
It can also do much more; if you need to print tables or prompt users for input, Spectre.Console
has you covered. But if you need to build a full-blown terminal UI, check out gui.cs
.
Running External Processes
The System.Diagnostics.Process
APIs built into .NET are clumsy, to say the least. Thankfully we have much better options these days! Here’s how to run git status
with the excellent SimpleExec
:
var (stdout, stderr) = await RunAsync("git", "status");
For a more powerful solution, check out CliWrap
; anything you can do in a Bash script, you can do in CliWrap
.