.NET
Reactive Framework
Programming
Software Development
RxJS

Good introduction to the .NET Reactive Framework

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Rx.NET, usually called Reactive Extensions for .NET, is a library for working with values that arrive over time. Instead of pulling values one by one, you subscribe to an IObservable<T> and compose streams with operators such as filtering, mapping, throttling, buffering, and merging. That makes Rx.NET especially useful for UI events, timers, message streams, and other asynchronous workflows.

The Core Mental Model

If IEnumerable<T> represents a pull-based sequence, IObservable<T> represents a push-based sequence. The producer decides when values arrive, and the consumer reacts to them.

An observable communicates through three signals:

  • 'OnNext for each value'
  • 'OnError if the sequence fails'
  • 'OnCompleted when the sequence ends normally'

A small example makes the model concrete:

csharp
1using System;
2using System.Reactive.Linq;
3
4class Program
5{
6    static void Main()
7    {
8        var numbers = Observable.Range(1, 5);
9
10        using var subscription = numbers.Subscribe(
11            x => Console.WriteLine($"Next: {x}"),
12            ex => Console.WriteLine($"Error: {ex.Message}"),
13            () => Console.WriteLine("Completed")
14        );
15    }
16}

This looks familiar if you know LINQ, and that is intentional. Rx borrows the compositional style of LINQ and applies it to event streams.

Transforming and Filtering Streams

Most practical Rx code is built from operators. You start with a source observable, then transform it.

csharp
1using System;
2using System.Reactive.Linq;
3
4class Program
5{
6    static void Main()
7    {
8        var query = Observable.Range(1, 10)
9            .Where(x => x % 2 == 0)
10            .Select(x => x * 10);
11
12        using var subscription = query.Subscribe(x => Console.WriteLine(x));
13    }
14}

This prints 20, 40, 60, 80, and 100.

The power of Rx is not the arithmetic itself. It is the fact that the same style works for button clicks, file system notifications, websockets, and timers.

Time Is a First-Class Part of the API

What makes Rx different from ordinary LINQ is that time-aware operators are built in.

For example, Throttle is useful when you only want the latest event after a quiet period, such as a search box.

csharp
1using System;
2using System.Reactive.Linq;
3using System.Reactive.Subjects;
4using System.Threading;
5
6class Program
7{
8    static void Main()
9    {
10        var input = new Subject<string>();
11
12        using var subscription = input
13            .Throttle(TimeSpan.FromMilliseconds(300))
14            .DistinctUntilChanged()
15            .Subscribe(text => Console.WriteLine($"Search: {text}"));
16
17        input.OnNext("r");
18        input.OnNext("rx");
19        Thread.Sleep(500);
20        input.OnNext("rx.");
21        Thread.Sleep(500);
22    }
23}

This is much easier than manually wiring timers, cancellation logic, and state flags.

Subject Is Useful, but Not the Whole Story

Beginners often discover Subject<T> first because it is easy to understand: it is both an observer and an observable. You can push values in and subscribe to values out.

That is convenient for demos and adapters, but it should not be your default abstraction for everything. In larger codebases, prefer higher-level observables created from events, timers, tasks, or other streams.

For example, turning a timer into an observable is straightforward:

csharp
1using System;
2using System.Reactive.Linq;
3
4class Program
5{
6    static void Main()
7    {
8        using var subscription = Observable.Interval(TimeSpan.FromSeconds(1))
9            .Take(3)
10            .Subscribe(x => Console.WriteLine($"Tick {x}"));
11
12        Console.ReadLine();
13    }
14}

This describes the behavior declaratively instead of manually coordinating timers and callbacks.

Scheduling and Threading

Rx does not eliminate threading concerns. It gives you tools to express them more clearly.

Two operators matter a lot:

  • 'SubscribeOn, which chooses where the subscription work starts'
  • 'ObserveOn, which chooses where observers receive notifications'

In UI apps, ObserveOn is often what brings values back to the UI thread safely. In server code, schedulers help coordinate background work without hand-written callback chains.

The rule is simple: Rx improves composition, but you still need to understand where code runs.

How to Learn Rx Without Getting Lost

A good path is:

  1. learn IObservable<T> and the three observer signals
  2. practice with Where, Select, Merge, Take, and Throttle
  3. understand subscription lifetime and disposal
  4. then learn schedulers and advanced subjects

Many beginners jump straight into advanced scheduler behavior and get overwhelmed. The sequence abstraction is the real foundation.

Common Pitfalls

The biggest pitfall is forgetting to dispose subscriptions. Long-lived subscriptions can leak memory and keep event sources alive unexpectedly.

Another issue is overusing Subject<T> as a global event bus. It is easy at first and messy later.

Developers also sometimes think Rx removes the need to reason about threads. It does not. It only gives you a better vocabulary for asynchronous composition.

Finally, start with simple operators. Complex Rx code built before the team understands the basics becomes hard to debug.

Summary

  • Rx.NET models asynchronous values as observable sequences.
  • 'IObservable<T> pushes OnNext, OnError, and OnCompleted signals to subscribers.'
  • Operators such as Where, Select, and Throttle make event-stream composition concise.
  • 'Subject<T> is useful, but not every reactive workflow should be built around subjects.'
  • Learn subscriptions, disposal, and scheduling gradually to avoid unnecessary complexity.

Course illustration
Course illustration

All Rights Reserved.