Skip to content

Generator: Create Observer Pattern #45

@JerrettDavis

Description

@JerrettDavis

Summary

Add a source generator that produces a complete implementation of the Observer pattern for event publication and subscription, with safe-by-default lifetimes and deterministic behavior.

The generator lives in PatternKit.Generators and emits code that is:

  • reflection-free
  • allocation-aware
  • explicit about threading policy
  • self-contained (no runtime PatternKit dependency)

Motivation / Problem

Observer is easy to misuse:

  • leaking subscriptions
  • nondeterministic invocation order
  • unclear exception behavior (one subscriber breaks others?)
  • ad-hoc concurrency policies

We want a generated implementation that makes the “rules of engagement” explicit and testable.


Supported Targets (must-have)

The generator must support:

  • partial class
  • partial struct
  • partial record class
  • partial record struct

Two consumption modes must be supported:

  1. Event type (a type represents one observable event stream).
  2. Event hub (a type groups multiple generated events).

Proposed User Experience

A) Single event, payload-based

[Observer]
public partial class TemperatureChanged { }

Generated (representative shape):

public partial class TemperatureChanged
{
    public IDisposable Subscribe(Action<Temperature> handler);
    public IDisposable Subscribe(Func<Temperature, ValueTask> handler);

    public void Publish(Temperature value);
    public ValueTask PublishAsync(Temperature value, CancellationToken ct = default);
}

B) Event hub (multi-event grouping)

[ObserverHub]
public static partial class SystemEvents
{
    [ObservedEvent]
    public static partial TemperatureChanged TemperatureChanged { get; }

    [ObservedEvent]
    public static partial ShutdownRequested ShutdownRequested { get; }
}

Generated semantics:

  • Each [ObservedEvent] property returns a singleton instance of that event stream.
  • Hub generation is optional, but if present must be deterministic and self-contained.

Attributes / Surface Area

Namespace: PatternKit.Generators.Observer

Core

  • [Observer] on an event stream type
  • [ObserverHub] on a hub type
  • [ObservedEvent] on hub properties

Configuration

ObserverAttribute suggested properties:

  • ObserverThreadingPolicy Threading (default: Locking)
  • ObserverExceptionPolicy Exceptions (default: Continue)
  • ObserverOrderPolicy Order (default: RegistrationOrder)
  • bool GenerateAsync (default: inferred)
  • bool ForceAsync (default: false)

Enums:

  • ObserverThreadingPolicy: SingleThreadedFast, Locking, Concurrent
  • ObserverExceptionPolicy: Stop, Continue, Aggregate
  • ObserverOrderPolicy: RegistrationOrder, Undefined

Semantics (must-have)

Subscriptions

  • Subscribe(Action<T>) returns an IDisposable token.
  • Dispose() unsubscribes deterministically.
  • Duplicate subscriptions are allowed in v1 (invoked multiple times).

Publishing

  • Default order: RegistrationOrder.
  • Publishing uses snapshot semantics (publish iterates a stable snapshot so modifications during publish do not affect the current cycle).

Exception policies

  • Continue (default): invoke all handlers; exceptions do not stop others.

    • v1: either swallow exceptions or route them to an optional user hook (see below). Must be explicit.
  • Stop: first exception aborts.

  • Aggregate: run all and throw an AggregateException (or return a result) at the end.

Recommended v1 behavior:

  • For sync Publish: Continue swallows by default but provides an optional hook: OnSubscriberError(Exception ex) if present.
  • For async PublishAsync: same semantics.

Async

  • Subscribe(Func<T, ValueTask>) must be supported.
  • PublishAsync invokes async handlers in deterministic order.
  • Cancellation token behavior: best-effort. If canceled before next invocation, stop and return canceled.

Threading policies

  • SingleThreadedFast: no locks; documented as not thread-safe.
  • Locking: lock around subscribe/unsubscribe; publish takes snapshot under lock.
  • Concurrent: thread-safe with concurrent primitives; ordering may degrade to Undefined unless extra work is done. Must be documented.

Optional advanced features (explicitly v2 unless trivial)

  • Weak subscriptions
  • Backpressure / queueing
  • Filters / predicate subscriptions
  • “Once” subscriptions

Diagnostics (must-have)

Stable IDs, actionable:

  • PKOBS001 Type marked [Observer] must be partial.
  • PKOBS002 Hub type marked [ObserverHub] must be partial and static.
  • PKOBS003 Hub property marked [ObservedEvent] has invalid shape (must be static partial and return the event stream type).
  • PKOBS004 Async publish requested but async handler shape unsupported.
  • PKOBS005 Invalid configuration combination (e.g., Concurrent + RegistrationOrder requires additional ordering guarantees).

Generated Code Layout

  • TypeName.Observer.g.cs
  • TypeName.ObserverHub.g.cs (if hub feature enabled)

Determinism:

  • internal storage deterministic where promised (especially for RegistrationOrder).

Testing Expectations

  • Subscribe/unsubscribe works and is idempotent.

  • Publish invokes in registration order.

  • Dispose during publish does not break iteration (snapshot semantics).

  • Exception policy behavior matches docs:

    • Continue
    • Stop
    • Aggregate
  • Threading policy tests:

    • Locking prevents basic races in a concurrent subscribe/publish stress test.
  • Async publish respects cancellation (best-effort).


Acceptance Criteria

  • [Observer] works for class/struct/record class/record struct.
  • Supports sync handlers and async handlers (ValueTask).
  • Deterministic ordering for RegistrationOrder modes.
  • Snapshot publishing semantics.
  • Clear exception policy with tests.
  • Explicit, documented threading policy.
  • Hub mode supported (optional but if implemented, fully tested).
  • Diagnostics cover invalid usage and config.

Metadata

Metadata

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions