dotnet/reactive · error · ArgumentNullException
ArgumentNullException(nameof(witness))
Error message
ArgumentNullException(nameof(witness))
What it means
The Do(observer, witness) overload throws ArgumentNullException when the witness argument is null. In this operator the witness is a second observer that receives a copy of every notification for side effects (logging, tracing); the library validates it up front because a null witness would crash the first time the source emits a notification. The primary observer and the witness are independently required.
Solutions
- Pass a real witness observer; if no side effect is desired, drop the witness overload and use Do(observer) or pass AsyncObserver.Create<TSource>(_ => default, _ => default, () => default) as a no-op witness.
- Verify that the factory/DI path producing the witness (e.g. a tracing observer built from a logger) cannot return null.
- If the witness is optional in your design, guard with `witness ?? noopWitness` before composing.
Example fix
// before var obs = AsyncObserver.Do<int>(downstream, traceObserver); // traceObserver null when tracing disabled // after var noopWitness = AsyncObserver.Create<int>(_ => default, _ => default, () => default); var obs = AsyncObserver.Do<int>(downstream, traceObserver ?? noopWitness);
Defensive patterns
Strategy: validation
Validate before calling
if (witness is null)
witness = AsyncObserver.Create<T>(_ => default, _ => default, () => default); // no-op witness
var result = AsyncObserver.Do(source, observer, witness); Type guard
static bool IsUsableWitness<T>(IAsyncObserver<T>? w) => w is not null;
Try / catch
try
{
var obs = AsyncObserver.Do(source, observer, witness);
}
catch (ArgumentNullException ex) when (ex.ParamName == "witness")
{
log.LogWarning("tracing witness missing; falling back to plain Do");
obs = AsyncObserver.Do(source, observer);
} Prevention
- Default optional witnesses to a no-op observer rather than null
- Verify logger/tracing initialization before building tracing observers
- Only call the witness overload when tracing is actually enabled
- Use nullable reference types to catch unassigned witness variables
When it happens
Trigger: Calling AsyncObserver.Do<TSource>(validObserver, null) — a non-null observer but a null IAsyncObserver<TSource> witness.
Common situations: The witness (often a logging/tracing observer) is resolved lazily and is null because a logger was not configured; a conditional tracing observer is only built in some branches but Do is called unconditionally; a typo passes the wrong variable that is null.
Related errors
- nameof(observer)
- nameof(onNext)
- throw new ArgumentNullException(nameof(source));
- throw new ArgumentNullException(nameof(sampler));
- throw new ArgumentNullException(nameof(source));
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/fd67bbd0372052d2.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Do.cs:157
throw new ArgumentNullException(nameof(onError));
if (onCompleted == null)
throw new ArgumentNullException(nameof(onCompleted));
return Create(
source,
(onNext, onError, onCompleted),
static (source, state, target) => source.SubscribeSafeAsync(AsyncObserver.Do(target, state.onNext, state.onError, state.onCompleted)));
}
}
public partial class AsyncObserver
{
public static IAsyncObserver<TSource> Do<TSource>(IAsyncObserver<TSource> observer, IAsyncObserver<TSource> witness)
{
if (observer == null)
throw new ArgumentNullException(nameof(observer));
if (witness == null)
throw new ArgumentNullException(nameof(witness));
return Create<TSource>(
async x =>
{
try
{
await witness.OnNextAsync(x).ConfigureAwait(false);
}
catch (Exception ex)
{
await observer.OnErrorAsync(ex).ConfigureAwait(false);
return;
}
await observer.OnNextAsync(x).ConfigureAwait(false);
},
async error =>
{View on GitHub (pinned to 94b5d5ab91)