{"record":{"id":"33dc0a5b1114dbeb","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-comparer-groupbyuntil","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'comparer')","messagePattern":"Value cannot be null\\. \\(Parameter 'comparer'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":40,"sourceCode":"            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n\n            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(\n                source,\n                (keySelector, durationSelector),\n                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupByUntil<TSource, TKey, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, IEqualityComparer<TKey> comparer)\n        {\n            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));\n            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));\n\n            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(\n                source,\n                (keySelector, durationSelector, comparer),\n                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector, state.comparer)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupByUntil<TSource, TKey, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity)\n        {\n            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));\n            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (capacity < 0)\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L22-L58","documentation":"The comparer overload of GroupByUntil requires a non-null IEqualityComparer<TKey> used to bucket elements into groups. A null comparer throws ArgumentNullException at call time in the guard at GroupByUntil.cs:40. Pass EqualityComparer<TKey>.Default when no custom comparison is needed.","triggerScenarios":"Calling AsyncObservable.GroupByUntil(source, keySelector, durationSelector, (IEqualityComparer<TKey>)null), typically when the comparer comes from an optional settings object that was not populated.","commonSituations":"Custom comparers injected via DI or configuration returning null when unset, caching an EqualityComparer lookup that missed, or case-insensitive key comparison setup forgotten.","solutions":["Use EqualityComparer<TKey>.Default in place of null","Ensure the configured/custom comparer is instantiated before the call","Drop the comparer argument entirely and use the (source, keySelector, durationSelector) overload"],"exampleFix":"// before\nvar groups = source.GroupByUntil(x => x.Name, g => g.Take(1), GetComparerOrNull());\n// after\nvar cmp = GetComparerOrNull() ?? EqualityComparer<string>.Default;\nvar groups = source.GroupByUntil(x => x.Name, g => g.Take(1), cmp);","handlingStrategy":"validation","validationCode":"var cmp2 = comparer ?? EqualityComparer<TKey>.Default;","typeGuard":"bool HasComparer<TKey>(IEqualityComparer<TKey> c) => c is not null;","tryCatchPattern":"try { var groups = source.GroupByUntil(ks, ds, cmp); } catch (ArgumentNullException ex) when (ex.ParamName == \"comparer\") { var groups = source.GroupByUntil(ks, ds, EqualityComparer<TKey>.Default); }","preventionTips":["Default comparer providers should return EqualityComparer<T>.Default, never null","Null-coalesce DI/config-injected comparers at the composition root","Drop the comparer argument if default equality suffices"],"tags":["argument-null","linq","async-rx","comparer"],"backgroundTag":"null-argument","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}