{"record":{"id":"390ed3491afaf009","repo":"dotnet/reactive","slug":"argumentnullexception-comparer-groupbyuntil","errorCode":null,"errorMessage":"ArgumentNullException: comparer","messagePattern":"ArgumentNullException: comparer","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":378,"sourceCode":"                throw new ArgumentOutOfRangeException(nameof(capacity));\n\n            return GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, EqualityComparer<TKey>.Default);\n        }\n\n        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity, IEqualityComparer<TKey> comparer)\n        {\n            if (observer == null)\n                throw new ArgumentNullException(nameof(observer));\n            if (subscription == null)\n                throw new ArgumentNullException(nameof(subscription));\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            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));\n\n            return GroupByUntil(observer, subscription, x => new ValueTask<TKey>(keySelector(x)), durationSelector, capacity, comparer);\n        }\n\n        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TElement, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<IGroupedAsyncObservable<TKey, TElement>, IAsyncObservable<TDuration>> durationSelector)\n        {\n            if (observer == null)\n                throw new ArgumentNullException(nameof(observer));\n            if (subscription == null)\n                throw new ArgumentNullException(nameof(subscription));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));\n            if (elementSelector == null)\n                throw new ArgumentNullException(nameof(elementSelector));\n            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n\n            return GroupByUntil(observer, subscription, keySelector, elementSelector, durationSelector, int.MaxValue, EqualityComparer<TKey>.Default);","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L360-L396","documentation":"GroupByUntil uses an IEqualityComparer<TKey> to route elements into groups. The six-argument overload at GroupByUntil.cs:365 throws ArgumentNullException when comparer is null, since the group map cannot compare keys without one.","triggerScenarios":"Calling GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, null) — often because a comparer resolved from DI/config was null, or the code intended a comparer-less overload but extra arguments selected this one.","commonSituations":"Custom key types whose comparer factory returned null; configurably-supplied comparers in generic pipelines; overload confusion after adding capacity/comparer parameters to a previous call.","solutions":["Pass EqualityComparer<TKey>.Default if no custom comparison is needed","Implement or register a comparer for the key type and ensure the factory cannot return null","Drop the comparer argument to use an overload that defaults to the standard comparer"],"exampleFix":"// before\nvar res = source.GroupByUntil(k => k, g => g.Take(1), 8, comparer /* null */);\n// after\nvar res = source.GroupByUntil(k => k, g => g.Take(1), 8, comparer ?? EqualityComparer<TKey>.Default);","handlingStrategy":"validation","validationCode":"if (comparer == null) comparer ??= EqualityComparer<TKey>.Default; // normalize before calling","typeGuard":"bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;","tryCatchPattern":"try { var res = source.GroupByUntil(keySelector, durationSelector, capacity, comparer); }\ncatch (ArgumentNullException ex) when (ex.ParamName == \"comparer\") { /* retry with EqualityComparer<TKey>.Default */ }","preventionTips":["Default comparer variables to EqualityComparer<TKey>.Default at declaration","Ensure comparer factories throw rather than return null","Call comparer-less overloads when custom comparison is not needed"],"tags":["csharp","argument-null","async-rx","groupbyuntil","icomparer"],"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"}