u/fedefex1

▲ 8 r/csharp+1 crossposts

A new take on reactive programming: backend signals with GraphQL-ish queries

Just a bit context:

Reactive programming in .NET spans Rx(R3) Observables, AsyncEnumerables, Raw events. I think the most powerful one is Rx, but using it properly on the backend requires a solid grasp of multithreading, async programming, and locking, on top of its own notoriously steep learning curve of operators. Once you've internalized it, though, it's an extremely powerful paradigm.

In angular (so, javascript, single threaded), the complexity of rx has made the angular team move toward signals (at least for common reactive tasks, not as a whole substitute). This choice has been really popular since thinking with signals is easier, and code is still reactive. So i created this library fedeAlterio/SignalsDotnet, to make signals also possible on .NET.

But i wanted to make a step further. Can signals be useful also on BE?

SignalsDotnet 3.2.0 on Backend

I think they are awesome. The main issue in backend scenario is that we are multi-threaded. While signals must operate in a single-threaded world. So my solution has been basically move them into a single threaded island (of course we can have several island), we can have several of these islands, and "single-threaded" here means serialized execution, not literally one dedicated thread per island (more details on that in the README).

Step 1) Declare a class, that would be our island

https://preview.redd.it/l1co1ysxhckh1.png?width=1425&format=png&auto=webp&s=69b7d2fcd1b7dee1fdb285f78aa6b7c28eadc7bc

Everything here will run single threaded (like Wpf), in custom Synchronization Context (so async-await is handled). Dependency injection will worl as well

Step 2) Register that class as a Signal Island, and expose a SSE endpoint to query it

https://preview.redd.it/5co4l438ickh1.png?width=1276&format=png&auto=webp&s=cd1a7210ce04574d39764925e4509ea658e3d136

Step 3) We can now query that island using GraphQL-like syntax. It's just an endpoint.

https://preview.redd.it/cdkwyu1jickh1.png?width=1736&format=png&auto=webp&s=125cea944dfd5eb902c0e2ea147cfbfe7b420901

This turns out to be more powerful than GraphQL subscriptions in a sense, since every signal (property) in the island is already its own subscription, the query is just combining them together.

Step 4) Just set properties, collections, dictionaries, whatever, on the island, and they'll be pushed out through the SSE endpoint. No need to declare a subscription because everything already is a subscription, the query is only there to combine them together.

Note: There are conversion methods from Observables, AsyncEnumerables, AsyncObservables. So we can Expose them in this way too.

Note 2: The ui is completely done with claude code and claude design. Code is a mess, but its a single html file. Since its only for debugging, I didn't want to spend time on that 😂

https://preview.redd.it/4cq7ckjcjckh1.png?width=1363&format=png&auto=webp&s=0349fcb5fdffe7581fa812ce3cb7b017d79ecb02

You can find it here fedeAlterio/SignalsDotnet. There is a playground app as well to get started easily.

u/fedefex1 — 1 day ago
▲ 13 r/csharp

SignalsDotnet 3.0

I just updated SignalsDotnet to 3.0, and it now supports source generators.

I think the library has become genuinely powerful: it lets you write reactive code without having to deal with reactive programming directly at all. With source generators it's cleaner than ever.

You mark a class (or a record) with [GenerateSignals] and every property becomes reactive, a signal. That means the getter and setter are tracked, and all the signal machinery kicks in automatically. The source generator also supports computed and async computed properties.

The library started as a port of Angular signals to .NET, but I think the power of C# (async locals, source generators, better async support) takes it to another level. It targets netstandard2.1, so it runs basically everywhere: WPF, Avalonia, Unity, Godot, Blazor.

Below is a runnable C# snippet as an example. As you can see, the whole system is reactive automatically: properties update themselves, code knows when to re-run, and so on. And when you need finer control, everything R3 observables offer is still right there.

The code below prints:

Total players 0
Player 1 joined
Total players 1
Total players 1
Total players 2
Total players 2
Best player is Player1 with score of 0
Best player is Player1 with score of 22
Best player is Player2 with score of 55
#:package SignalsDotnet@3.0.0

using System.Collections.Immutable;
using R3;
using SignalsDotnet;

var player1 = new Player { Name = "Player1", Score = 0 };
var player2 = new Player { Name = "Player2", Score = 0 };

var game = new Game();
Effect.Create(() =>
{
    if (game.PlayersByName.ContainsKey(player1.Name))
        Console.WriteLine("Player 1 joined");
});

IAwaitable<bool> player2Joined = Signal.WaitForChangeAsync(() => game.PlayersByName.ContainsKey(player2.Name));

Effect.Create(() => Console.WriteLine($"Total players {game.PlayersByName.Count}"));
game.AddPlayer(player1);
game.AddPlayer(player2); // this completes the awaitable
await player2Joined;

Effect.Create(() =>
{
    if (game.BestPlayer is not null and var bestPlayer)
        Console.WriteLine($"Best player is {bestPlayer.Name} with score of {bestPlayer.Score}");
});

Observable<ImmutableArray<Player>> scoreboardHistory = Signal.ComputedObservable(() => game.Scoreboard); // A notification for every scoreboard change

player1.Score = 22;
player2.Score = 55;

Console.ReadLine();

[GenerateSignals]
public partial record Player
{
    public partial string Name { get; set; }
    public partial int Score { get; set; }
}

public partial class Game
{
    private readonly IDictionary<string, Player> _playersByName = new DictionarySignal<string, Player>();
    public IReadOnlyDictionary<string, Player> PlayersByName => _playersByName.AsReadOnly();

    public void AddPlayer(Player player) => _playersByName.Add(player.Name, player);
    public void RemovePlayer(Player player) => _playersByName.Remove(player.Name);

    [Computed] ImmutableArray<Player> ComputeScoreboard() => [.. _playersByName.Values.OrderByDescending(x => x.Score)];
    [Computed] Player? ComputeBestPlayer() => Scoreboard.FirstOrDefault();
    [Computed] Player? ComputeWorstPlayer() => Scoreboard.LastOrDefault();
}

It runs as a single file on .NET 10. Save it as game.cs and run dotnet run --file game.cs. No csproj needed.

GitHub: https://github.com/fedeAlterio/SignalsDotnet NuGet: https://www.nuget.org/packages/SignalsDotnet

reddit.com
u/fedefex1 — 9 days ago
▲ 82 r/csharp

Why ReactiveUI Just used my code without mention it?

ReactiveUI discussion

A bit of context:

Some time ago I wrote R3Async, a ReactiveX library for AsyncObservable. I started that project because development of AsyncRx.NET in dotnet/reactive repo appeared to have slowed down, and the maintainers understandably seemed to have limited time to dedicate to it. I even reached out to let them know I would be happy to continue development for free if they were interested. I had no particular expectations—I simply wanted a usable asynchronous reactive extensions library to exist in the .NET ecosystem (also just for use it in my own code).

In the meantime, I came across Cysharp's R3 implementation and, in my opinion, it was a significant improvement over the traditional Rx design. That led me to create an asynchronous version of R3 instead.

As you can imagine, this is not a trivial problem space. Rx is already complex on its own, and async/await introduces another layer of complexity. Combining the two makes both the design and implementation particularly challenging, something that has also been acknowledged by developers involved in dotnet/reactive in the past. You have to deal with async locks, reentrancy of async locks, how async context work, Async locals, and so on.

Despite that, I was confident it could be done, and eventually I released the library. It is backed by a comprehensive test suite as well.

The reason I mention all of this is that I did not immediately decide to create a competing library. First, I looked for existing solutions. Then I tried to contribute to them (I have some pull requests in dotnet/reactive for AsyncRx). Only after seeing that development was progressing slowly did I decide to create and publish my own implementation under the MIT license.

The issue

A few days ago, I came across the ReactiveUI Primitives repository. As described, it is another implementation inspired by R3, with different naming conventions and some performance optimizations.

I noticed that it also includes an asynchronous implementation. I reviewed it out of curiosity, since I had previously implemented R3Async and I was interested in how others approached the same problem.

After reviewing the code, I observed that the implementation appears to closely resemble R3Async in structure and design, with some modifications and performance-related changes.

I want to be clear that reuse itself is not the issue. R3Async is released under the MIT license specifically to allow reuse and modification. The concern is that I did not find attribution to R3Async or to its repository in the codebase.

Examples

- CancelableTaskSubscription R3Async ReactiveUI: Here there is a comment in ReactiveUI code where they said they replaced an AsyncLocal field to reduce allocations. There is not AsyncLocal field in their git history, but there is in R3Async as you can see. Note that this optimization is also wrong since, this code will hang in their version:

https://preview.redd.it/yzim1ga0st5h1.png?width=1264&format=png&auto=webp&s=0db9180297afb08e8e655a13cef88cfffcd72528

- BaseAyncObservable. ReactiveUI, R3Async. Very similar code with same optimizations as above. But those optimizations are wrong for same reasoning

- Subjects: ReactiveUI, R3Async

- CreateAsBackroundJob. (I just included because the name is a bit odd)

https://preview.redd.it/udixxcqqbn5h1.png?width=911&format=png&auto=webp&s=4d20b9c83525e81a85d4511888289055b16f6a41

- AsyncContext. ReactiveUI, R3Async

- Operators implementation. Just search them in the repo.

- How the code is implemented in general. I invite you to just navigate between both repositories

Other examples

- WaitCompletionAsync: R3Async ReactiveUI

- ToAsyncEnumerable (same idea of using a channel factory and exactly same implementation): R3Async, ReactiveUI

- TakeUntil: R3Async ReactiveUI (Note also the same CompletionSignalDelegate, this is not a standard Rx solution)

- ObserveOn: R3Async ReactiveUI (Same AsyncContext)

I didn't include all the operators because I assume that many of them are standard implementations and therefore more likely to be similar, even when the code is extremely close.

Notes

I don’t know whether this was done intentionally or if it’s another case of code being reproduced through LLM-assisted workflows without proper attribution. Either way, it’s understandably frustrating.

What makes it more disappointing is that I’m not a known developer, so even a simple acknowledgment that my work was used or inspired their implementation would have meant a lot to me and helped validate the effort I put into it

reddit.com
u/fedefex1 — 3 months ago
▲ 0 r/csharp

Why Signals in C# are not a thing?

I think Signals are a much better approach to frontend state management than what all .NET UI frameworks currently offer.

They’re becoming mainstream in the JavaScript world, but for some reason they still aren’t really a thing in .NET UI frameworks like Avalonia, MAUI, Unity, Godot, etc., where we still rely heavily on INotifyPropertyChanged, bindings, commands, Rx, and a lot of boilerplate.

Look for example at this library I built: fedeAlterio/SignalsDotnet

And the potential this pattern could have in a framework like Avalonia (see for example Signals integrated in AvaloniaProperties · AvaloniaUI/Avalonia · Discussion #20962). You basically get automatic computed properties and dependency tracking without manually wiring events everywhere.

And no, source generators around INotifyPropertyChanged are not really the same thing. How would they even handle something like (they should account AsyncLocals..):

Signal.AsyncComputed(async (ct) =>
{
    if(!page.Isloaded.Value)
       return null;    

    if (!someService.IsLoaded.Value)
        return null;

    return await GetUserAsync(MyId.Value, ct);
});

The thing we depend on is not in the same ViewModel, is outside in a service. Also who said we need a VM? Maybe we are in Unity and we just need a way to "React" to a change. Also who said we are in FE? Maybe we are in BE and we are in a (single threaded) "reactive" state service). This would simplify things a LOT, and just needs signalR to notify clients of Signals changes..

Every JS dev who sees INotifyPropertyChanged for the first time would probably laugh, and I’m not surprised.

u/fedefex1 — 3 months ago