I thought desktop app development was "dead" - why so many Maui / Avalonia / Uno developers?

Consider this a "me stepping out of my distributed systems / web app bubble" question. If you casually talk on X or even at developer conferences, there's very little talk about the future of native desktop applications or even people discussing what they're building.

Yet I see tons of evidence based on the success of Avalonia and Uno that there's huge demand for technology in this area still!

What are all of these desktop app developers working on? Is it all just retro-fitting old WPF apps? What are the new ones you're building?

And where are your great conference talk submissions!

reddit.com
u/Aaronontheweb — 19 hours ago

Have you ever used CsCheck? Maybe you should!

I've been a happy FsCheck user for many years, even though I program primarily in C# and not F#. I used it both for property and model-based testing.

I'd been meaning to check out CsCheck for doing the same thing, but aimed natively at C# developers. So I gave it a try recently and liked it!

If you are not familiar with model / property-based testing, I wrote some blog posts on this ~10 years ago using FsCheck with C# Writing Better Tests Than Humans Can Part 1 Part 2 - but the basic idea is you can assert that a property or a model holds true across a randomly generated set of inputs.

Effectively the property-based testing framework generates hundreds or thousands of random tests to exercise that these properties hold true - take for instance the double-buffering system we use for doing TUI rendering in Termina:

[Fact]
public void IdenticalBuffers_ProduceNoChanges() =>
    // If nothing changed, the diff must be empty. A false positive here would redraw the whole
    // screen every frame and bring back the flicker the diff engine removes.
    (from w in Gen.Int[1, 8] from h in Gen.Int[1, 6] from a in CellGen.Array[w * h] select (w, h, a)).Sample(t =>
    {
        var (w, h, a) = t;
        var buf = Build(w, h, a);
        var copy = new FrameBuffer(w, h);
        copy.CopyFrom(buf);
        Assert.Empty(buf.GetChangedCells(copy));
        Assert.Empty(buf.GetChangedRuns(copy));
    }, iter: Iter);

I don't show the full code from this snippet, but we generate a range of random inputs and then assert that an identical copy of the random input always produces a no-op inside the double buffer diffing system - therefore, no cells should require an update and the screen doesn't require a re-render.

We can use CsCheck to do fancier things than testing for a no-op - here's another example:

    private static readonly string[] CellPalette =
    {
        "a", "B", "7", "#", " ", "z",              // narrow (1 column)
        "中", "文", "あ", "한", "A",                // wide (2 columns)
        Cp(0x65, 0x0301), Cp(0x6F, 0x0308),        // base + combining mark (1 column; the mark is 0)
        Cp(0x1F600), Cp(0x1F389), Cp(0x20000),     // supplementary (surrogate pairs)
        Cp(0x2600, 0xFE0F), Cp(0x270B, 0xFE0F),    // emoji + variation selector (2 columns)
        Cp(0x31, 0xFE0F, 0x20E3),                  // keycap sequence (2 columns)
    };


    // A text built by joining 0..8 whole cells. Boundaries are clean by construction.
    private static readonly Gen<string> CellText =
        Gen.OneOfConst(CellPalette).List[0, 8].Select(parts => string.Concat(parts));


    // A hostile UTF-16 code unit: arbitrary chars, plus specific escapes, controls, selectors, and
    // both halves of surrogate pairs (so lone, unpaired surrogates appear too).
    private static readonly Gen<char> FuzzChar = Gen.OneOf(
        Gen.Char,
        Gen.OneOfConst(Esc, '[', ']', Bel, 'm', '\n', '\t', '\r', '\0', ' ', 'a', Cjk, Vs16, Keycap, Zwj),
        Gen.OneOfConst('\uD83D', '\uDE00', '\uD800', '\uDBFF', '\uDC00', '\uDFFF'));


    // A text of 0..24 hostile code units. May contain ill-formed UTF-16.
    private static readonly Gen<string> FuzzText =
        FuzzChar.Array[0, 24].Select(chars => new string(chars));


    // Either kind of text.
    private static readonly Gen<string> AnyText = Gen.OneOf(CellText, FuzzText);

A Gen is a generator for some random data - and it has some important properties: namely that in a more complex model based test we can reduce complex test cases to their smallest possible reproduction. So these aren't just wrappers around Random, there's more to it than that - as Anthony Lloyd (the author) explains: https://github.com/AnthonyLloyd/CsCheck/blob/master/Comparison.md#integrated-shrinking

These are data sources for tests aimed at character / text rendering. Some unicode characters in Chinese languages actually use 2x the rendering width and we'd had bugs reported related to this before. So, we can create some custom Gen data sources that will use some of these characters as random inputs.

We can then feed this into a test:

[Fact]
public void A2_CellWidth_IsZeroOneOrTwo() =>
        // A terminal cell is 0, 1, or 2 columns. A value outside that range means a glyph that
        // cannot be placed, so later column math (wrapping, cursor) would be wrong.
        AnyText.Sample(s =>
        {
            foreach (var c in DisplayWidth.EnumerateCells(s))
                Assert.InRange(c.ColumnWidth, 0, 2);
        }, iter: Iter);

In this case we assert that the DisplayWidth correctly computes that any character in the universal set of chars can only have a width of 0,1, or 2. This includes some of the hostile characters and escape codes that are lumped inside the AnyText generator.

Now that LLMs are generating a substantial portion of all new code, it's equally important that we have stronger tools to test and verify its correctness. Property and model-based testing tools like CsCheck are more than up to the task. You should give them a try!

reddit.com
u/Aaronontheweb — 2 days ago

August 2026 Edition: Promote Your Local .NET Meetups

Promote your local .NET user group / meetups here.

Please include:

  • Location and Time
  • Topic
  • Link to the specific event
  • Anything else that would be great for attendees to know

You do not need to be the organizer of the meetup, just an enthusiast!

Also, if you need help launching a local .NET Meetup this is one of the things the .NET Foundation can help with! Please see .NET Meetups @ .NET Foundation

reddit.com
u/Aaronontheweb — 3 days ago

Building a Distributed Job Scheduler with Akka.NET

I wrote a blog post / YouTube video / OSS code sample at the very end of July to cover a scenario that one of our users ran into building a distributed job scheduler that can distribute, long-running, data-intensive jobs across an auto-scalable pool of worker processes without starting / stopping in-progress jobs as the pool grows during peak demand.

That latter part, "not rebalancing in-progress jobs," is what eliminates a lot of off-the-shelf distribution strategies like the types implemented by Microsoft Orleans and Akka.NET's Akka.Cluster.Sharding from consideration. Those frameworks are really aimed at distributing stateful "entities" - actors with important business state that live forever (often, but not always) are only intermittently busy in short bursts.

Distributing a "job" is a very different type of workload: these are tasks with a finite, well-defined lifespan in which they are intensely busy from beginning to end. Re-distributing a 10 minute job when it's 8 minutes into execution turns these into 18, 20 minute jobs potentially depending on a bunch of factors (can the job be started immediately?)

Earlier in my career I used some Akka.NET and Akka.Cluster primitives to solve this exact type of problem in the banking industry: running bank CFO "asset line management" jobs all in the final 48 hours of the month in order to meet the monthly reporting requirements and have enough data to actually complete them.

The basic formula, which I expand on in the post with code samples:

  1. Establish the ability to "size" jobs early - how many units of compute is Job A relative to Job B? This is a lot easier to define than it sounds. If you're doing asset line management, your "size" is typically the total number of assets that need to be analyzed (i.e. rows.) If you're doing call transcription it might be the size of the audio file. This should be a O(1) operation.
  2. Create a Cluster singleton (1 instance globally) who is responsible for: 2a. Managing and persisting the queue of jobs-to-be-done AND the parties who own them 2b. Subscribing to live Akka.Cluster topology update events (nodes joining, leaving, or having trouble) - this impacts our distribution system. 2c. Persisting the snapshot of which worker nodes are running which jobs 2d. Tracking progress updates across these jobs + reporting that to original callers 2e. Re-constituting all of this state after a restart using Akka.Persistence
  3. Have job receivers running on each node responsible for receiving the "job definition" and transforming that into a live execution.
  4. Have the job executors report progress back to the tracker (our singleton)

The distributed systems space tends to get dominated by stateful entity type-work, but running a large number of concurrent "jobs" is an equally tricky and nuanced space so I thought it merited some attention as well as some productionization details that might not be obvious!

Post: "Building a Distributed Job Scheduler with Akka.NET"

Repo: https://github.com/Aaronontheweb/akka.net-custom-job-scheduling

Video: Video: Building a Distributed Job Execution Platform with Akka.NET

u/Aaronontheweb — 3 days ago

Techniques for getting LLMs to produce better .NET programs

I've been AI-pilled really since the beginning of 2025 when I first gave Cursor a try, and it's been an intense, strange journey ever since.

I've written some blog posts about my experience with LLM coding that I'll link to in the comments, but I wanted to venture out and ask developers on here - what's been some techniques that have helped you get better .NET output / programs from large language models?

reddit.com
u/Aaronontheweb — 4 days ago

My dumb little Manhattan Project: ShellSyntaxTree: pure C# abstract syntax tree representation for parsing shell commands. Supports bash and PowerShell.

I've been working on this project for a few months to help represent bash and PowerShell commands as abstract syntax trees, so I can take a complex shell command generated by an LLM inside Netclaw like this:

cd ~/repositories/netclaw-dev/netclaw && grep -rn "class McpServerEntry\|record McpServerEntry\|GrantCategory" src/Netclaw.Daemon/Mcp/ --include="*.cs" -l | head; grep -rn "GrantCategory" src/Netclaw.Configuration --include="*.cs" | head

and help turn that into:

CWD: ~/repositories/netclaw-dev/netclaw

Verbs executed in this CWD: grep, head;

Conclusion: this is a read-only command and is safe to execute (Netclaw does this part using stored approvals provided by the human-in-the-loop)

The hard part is extracting the patterns from all of these chained commands and tracing the directories they flow through AND dealing with lots of shell-specific quirks like heredocs and piping operators.

The cool part is though, you can visualize most bash scripts using the trees ShellSyntaxTree creates, such as https://github.com/Aaronontheweb/ShellSyntaxTree#samples

github.com
u/Aaronontheweb — 6 days ago

👋 Welcome to r/moderndotnet - Introduce Yourself and Read First!

Hey everyone! I'm u/Aaronontheweb, a founding moderator of r/moderndotnet.

I started this sub because I think the .NET community deserves better than what Reddit's been giving it.

Let's be honest about why this place exists. The main .NET sub has become a place where serious technical discussions get buried or downvoted to zero, the moderation is either asleep or enforcing rules that make no sense, and a lot of good developers have just stopped posting altogether.

If you want to blanket downvote or flame someone because you don't like the Magic the Gathering game someone made with WinForms or because you're mad that someone didn't follow double-upside down hexagonal ports-and-adapters DDD clean code enterprise ASP .NET Core template or whatever, you should continue participating over there.

This is the alternative.

What we're about:

  • Real technical discussion - you do not have to adhere to Microsoft orthodoxy here. You want to implement your own GC, ship third party OSS software, or *gasp* - do things in F#, you are welcome to discuss that here.
  • Civility, enforced - if your comments sound in any way reminiscent of a Stack Overflow moderator, you will be shown the door. Disagree all you want, but don't be a prick.
  • Helpful, not hostile - beginner questions are fine.
  • Actual effort - AI slop will be auto-modded and blocked. No one wants to read something you didn't put the time into writing yourself. GitHub repositories that aren't at least 90 days old will be blocked.

What to Post
Some ideas on what to post here:

  • Blog posts and projects you've been working on (within reason) - you want to share a project you've worked on or a blog post you wrote? As long as it looks like there has been (1) sufficient human effort and (2) sufficient prior participation in this sub by the OP, it will be tolerated. Don't overdo it though.
  • Questions about .NET patterns, practices, libraries, etc - great, we love these.
  • Benchmarks, success / failure stories, and real-world experiences
  • Notes about AI and .NET - not promoting general purpose AI tools, but things _specific_ to .NET and .NET experiences.
  • Announcements about new releases, updates - again, keep it within reason just like blog posts. Not every tiddly-wink release needs to be announced on here.

Community Vibe
Don't be a jerk. Stay on-topic. Don't post AI slop (not the same as posting about AI, which is fine); and have fun.

How to Get Started

  1. Introduce yourself in the comments below.
  2. Post something today! Even a simple question can spark a great conversation.
  3. If you know someone who would love this community, invite them to join.
  4. Interested in helping out? We're always looking for new moderators, so feel free to reach out to me to apply.

We're starting from ground zero - please help build a better culture than the other sub. Invite the .NET devs you actually respect. And if you want to help moderate, message me — we're looking for people who care enough to show up.

Welcome to the modern .NET community.

reddit.com
u/Aaronontheweb — 6 days ago

Pruning cold subscribers

I maintain a ~10,000 person or so mailing list of subscribers to our company / open source project. A fairly standard inbound marketing operation I've been running for 11 years or so.

The guidance from email marketing platforms like Kit these days is to focus on the rate of active engagement of subscribers, versus worrying about the overall size of the list. Ok, I get that; times have changed since I started this business. I suppose this should help me land in the focused inbox more often and have better deliverability rates.

I've been pruning cold subscribers or so for 6 months - when am I supposed to be able to measure some benefit from doing this?

reddit.com
u/Aaronontheweb — 17 days ago
▲ 2 r/dotnet

Why are, or aren't, you using state machines?

I'm doing some research for some educational material I'm planning for some Akka.NET documentation and tutorials in the future.

Some context:

The actor model / actors are classically known as more of a concurrency / real-time programming thing - i.e. building a back-end for multiplayer video games, industrial IOT, finance, sports betting, etc...

But a much more common application for actors in business applications is to model complex entities and coordination patterns as state machines.

These aren't necessarily super high traffic applications, but what they have are lots of business rules + synchronization + coordination problems related to competing reads / writes / other activities happening across different application instances (web farm stuff.)

Moving that machinery into a state machine makes these problems easier to reason about, test, observe, and process safely.

Question:

Why (and how) or why aren't you using state machines in your applications today? If you aren't using state machines, what are the alternatives you've chosen and why?

u/Aaronontheweb — 23 days ago
▲ 2 r/dotnet

I made an abstract syntax tree parser for Bash in C#

Blazor WASM visualizer of the AST w/ Mermaid

I'm working on a .NET agents project (https://netclaw.dev) and one of its features allows them to execute shell commands.

In order to make agent execution comply with some rules (determined by the end-user + security policy) I needed some way of reasoning about "what is this command really doing and which directory is it doing it in?" so I could surface that information in an approval prompt to the end-user.

There are some native libraries that do this, but I'm hoping to make my project AOT-compatible in the future so I had Claude grok a corpus from thousands of commands my agent has attempted to run and built a C# program that could create an abstract syntax tree representation that could definitely determine:

  1. What are the real command verb + noun pairs the agent is requesting to execute?
  2. Which directory(ies) are they being executed in - this requires doing things like tracking the implicit flow of the current working directory.

I've dogfooded this over the course of the past week or so with several thousands more commands and it works great. The Blazor WASM sample I have on screen is just a visualizer of what the AST yields and serves no practical purpose other than being fun.

The library doesn't support things like bash functions and evaluating shell file references because that's kind of out of scope for what I need (evaluating inline CLI commands) - so if you try pasting a `.sh` file it'll choke on those.

For instance though:

using ShellSyntaxTree;

var parser = new BashParser();
var parsed = parser.Parse("cd /repo && rm /etc/passwd");

if (parsed.IsUnparseable)
{
    // Safe-fail: prompt the user, deny the command, etc.
    Console.WriteLine($"can't model: {parsed.UnparseableReason}");
    return;
}

foreach (var clause in parsed.Clauses)
{
    Console.WriteLine($"{clause.Operator} {clause.Verb.Joined}");

    foreach (var arg in clause.Args.Where(a => a.IsPath))
    {
        var marker = arg.IsCwdAttribution ? "↳ cwd" : "  path";
        Console.WriteLine($"    {marker}: {arg.Resolved}");
    }

    foreach (var redirect in clause.Redirects.Where(r => !r.IsDynamicSkip))
    {
        Console.WriteLine($"    {redirect.Direction}: {redirect.Target}");
    }
}

Will produce (shows the propagation of the current working directory):

None cd
      path: /repo
AndIf rm
    ↳ cwd: /repo
      path: /etc/passwd

I'm working on adding a PowerShell flavor to this library next so I can do the same types of things on Windows shells.

Repo is here: https://github.com/Aaronontheweb/ShellSyntaxTree

reddit.com
u/Aaronontheweb — 3 months ago