Skip to main content

Getting Started

Installation​

dotnet add package ZeroAlloc.AsyncEvents

Declare a handler​

AsyncEventHandler<TArgs> is a struct. Declare it as a private field:

private AsyncEventHandler<OrderPlacedArgs> _orderPlaced = new(InvokeMode.Parallel);

Register a handler​

_orderPlaced.Register(OnOrderPlacedAsync);

private async ValueTask OnOrderPlacedAsync(OrderPlacedArgs args, CancellationToken ct)
{
await SendEmailAsync(args.OrderId, ct);
}

Or with a lambda:

_orderPlaced.Register(async (args, ct) => await SendEmailAsync(args.OrderId, ct));

Invoke​

await _orderPlaced.InvokeAsync(new OrderPlacedArgs(orderId), cancellationToken);

Returns ValueTask — await it or fire-and-forget with _ =.

Unregister​

_orderPlaced.Unregister(OnOrderPlacedAsync);

Use += / -= operators as shorthand:

_orderPlaced += OnOrderPlacedAsync;
_orderPlaced -= OnOrderPlacedAsync;

Expose as a C# event​

Use explicit add/remove accessors to expose the handler as a standard C# event:

private AsyncEventHandler<OrderPlacedArgs> _orderPlaced = new(InvokeMode.Parallel);

public event AsyncEvent<OrderPlacedArgs> OrderPlaced
{
add => _orderPlaced.Register(value);
remove => _orderPlaced.Unregister(value);
}

Callers subscribe and unsubscribe with += / -= as usual; internally it routes to Register/Unregister.

Use the source generator​

Instead of writing the add/remove accessors manually, annotate the field with [AsyncEvent] on a partial class:

public partial class OrderService
{
[AsyncEvent(InvokeMode.Parallel)]
private AsyncEventHandler<OrderPlacedArgs> _orderPlaced = new(InvokeMode.Parallel);

// Generated automatically — no need to write this:
// public event AsyncEvent<OrderPlacedArgs> OrderPlaced { ... }
}

Apply [AsyncEvent] on the class to cover all AsyncEventHandler<TArgs> fields at once:

[AsyncEvent(InvokeMode.Parallel)]
public partial class OrderService
{
private AsyncEventHandler<OrderPlacedArgs> _orderPlaced = new(InvokeMode.Parallel);
private AsyncEventHandler<ItemShippedArgs> _itemShipped = new(InvokeMode.Parallel);
}

See Source Generator for full details including field-overrides class mode.

Cancelable events​

For sequential pipelines where an early handler should abort the rest, use CancelableAsyncEventHandler<TArgs> with CancelEventArgs. See Cancelable Events for details.