{"record":{"id":"1a6b0d3e79a56bd7","repo":"dotnet/reactive","slug":"comparer-groupbyuntil","errorCode":null,"errorMessage":"comparer","messagePattern":"comparer","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":184,"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, ValueTask<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, ValueTask<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":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L166-L202","documentation":"The comparer overload of GroupByUntil requires an IEqualityComparer<TKey> to group keys; unlike some other overloads there is no default-comparer fallback here, so a null comparer throws ArgumentNullException with parameter name \"comparer\" (message \"Value cannot be null. (Parameter 'comparer')\") during eager validation. The exception is thrown synchronously at the call, before any subscription begins.","triggerScenarios":"Calling `source.GroupByUntil(keySelector, durationSelector, null)` where the comparer variable is null — e.g. an injected IEqualityComparer<TKey> not registered in DI, a custom comparer returned null from a factory, or a typo passing null where a comparer instance was intended.","commonSituations":"DI containers missing an IEqualityComparer<TKey> registration; comparer instances conditionally initialized; refactoring where a default comparer overload call was switched to the comparer overload without supplying an instance.","solutions":["Pass a valid comparer instance, e.g. `EqualityComparer<TKey>.Default`, or your custom IEqualityComparer<TKey>.","If you do not need custom key equality, use the two/three-argument overloads of GroupByUntil that omit the comparer so defaults apply.","Ensure DI/configuration actually provides the comparer; fail at startup if the registration is missing.","Catch ArgumentNullException at the call site if a missing comparer is an expected, recoverable condition."],"exampleFix":"// before\nvar grouped = source.GroupByUntil(x => x.Key, g => g.TakeUntil(done), null);\n// after\nvar grouped = source.GroupByUntil(x => x.Key, g => g.TakeUntil(done), EqualityComparer<string>.Default);","handlingStrategy":"validation","validationCode":"if (comparer is null)\n    comparer = EqualityComparer<TKey>.Default; // or throw explicitly","typeGuard":"static bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;","tryCatchPattern":"try\n{\n    var grouped = source.GroupByUntil(keySelector, durationSelector, comparer);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == \"comparer\")\n{\n    // fall back to EqualityComparer<TKey>.Default\n}","preventionTips":["Register IEqualityComparer<TKey> implementations in your DI container and validate at startup.","Default to EqualityComparer<TKey>.Default when custom key equality is not required.","Use comparer overloads only when a comparer instance is guaranteed non-null.","Annotate comparer parameters/fields as non-nullable to surface missing initialization at compile time."],"tags":["null-reference","comparer","argument-validation","async-rx","groupbyuntil"],"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"}