dotnet/reactive · error · ArgumentNullException
ArgumentNullException(nameof(comparer))
Error message
ArgumentNullException(nameof(comparer))
What it means
The sync-keySelector DistinctUntilChanged<TSource,TKey> overload throws ArgumentNullException when the IEqualityComparer<TKey> comparer is null. The operator calls comparer.Equals on every notification, so a null comparer would crash mid-stream; it is rejected eagerly instead.
Solutions
- Pass EqualityComparer<TKey>.Default when you want default semantics instead of null.
- Pass a concrete comparer instance (e.g. StringComparer.Ordinal).
- If the comparer is resolved at runtime, fall back to EqualityComparer<TKey>.Default when resolution returns null.
Example fix
// before var op = AsyncObserver.DistinctUntilChanged(observer, x => x.Name, cfg.Comparer); // null // after var cmp = cfg.Comparer ?? EqualityComparer<string>.Default; var op = AsyncObserver.DistinctUntilChanged(observer, x => x.Name, cmp);
Defensive patterns
Strategy: validation
Validate before calling
var cmp2 = comparer ?? EqualityComparer<TKey>.Default; var res = AsyncObserver.DistinctUntilChanged(observer, keySelector, cmp2);
Type guard
static IEqualityComparer<TKey> OrDefault<TKey>(IEqualityComparer<TKey>? c) => c ?? EqualityComparer<TKey>.Default;
Try / catch
try { var res = AsyncObserver.DistinctUntilChanged(observer, keySelector, comparer); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { comparer = EqualityComparer<TKey>.Default; } Prevention
- Use EqualityComparer<T>.Default instead of null for default equality.
- Wrap custom comparer resolution in a helper that never returns null.
- Add unit tests that exercise comparer resolution from config/DI.
When it happens
Trigger: Calling DistinctUntilChanged(observer, keySelector, null) with an explicit comparer argument that is null.
Common situations: A comparer resolved from a registry/DI container that returned null; a nullable comparer field; callers intending 'default comparer' passing null instead of EqualityComparer<TKey>.Default.
Related errors
- Value cannot be null. (Parameter 'comparer')
- comparer
- Value cannot be null. (Parameter 'comparer')
- comparer
- Value cannot be null. (Parameter 'disposable1')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/00b7a0f5c4c13399.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/DistinctUntilChanged.cs:137
public static IAsyncObserver<TSource> DistinctUntilChanged<TSource, TKey>(IAsyncObserver<TSource> observer, Func<TSource, ValueTask<TKey>> keySelector)
{
if (observer == null)
throw new ArgumentNullException(nameof(observer));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
return DistinctUntilChanged(observer, keySelector, EqualityComparer<TKey>.Default);
}
public static IAsyncObserver<TSource> DistinctUntilChanged<TSource, TKey>(IAsyncObserver<TSource> observer, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
if (observer == null)
throw new ArgumentNullException(nameof(observer));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (comparer == null)
throw new ArgumentNullException(nameof(comparer));
var hasCurrentKey = false;
var currentKey = default(TKey);
return Create<TSource>(
async x =>
{
var key = default(TKey);
try
{
key = keySelector(x);
}
catch (Exception ex)
{
await observer.OnErrorAsync(ex).ConfigureAwait(false);
return;
}View on GitHub (pinned to 94b5d5ab91)