{"record":{"id":"178e2500c37db415","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-comparer-groupby","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/GroupBy.cs","lineNumber":262,"sourceCode":"\n            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TElement>>.From(\n                source,\n                (keySelector, elementSelector, capacity),\n                static (source, state, observer) => GroupByCore(source, observer, (o, d) => AsyncObserver.GroupBy(o, d, state.keySelector, state.elementSelector, state.capacity)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, int capacity, 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 (elementSelector == null)\n                throw new ArgumentNullException(nameof(elementSelector));\n            if (capacity < 0)\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));\n\n            return CreateAsyncObservable< IGroupedAsyncObservable<TKey, TElement>>.From(\n                source,\n                (keySelector, elementSelector, capacity, comparer),\n                static (source, state, observer) => GroupByCore(source, observer, (o, d) => AsyncObserver.GroupBy(o, d, state.keySelector, state.elementSelector, state.capacity, state.comparer)));\n        }\n\n        private static async ValueTask<IAsyncDisposable> GroupByCore<TSource, TKey, TElement>(IAsyncObservable<TSource> source, IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>> observer, Func<IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>>, IAsyncDisposable, (IAsyncObserver<TSource>, IAsyncDisposable)> createObserver)\n        {\n            var d = new SingleAssignmentAsyncDisposable();\n\n            var (sink, subscription) = createObserver(observer, d);\n\n            var inner = await source.SubscribeSafeAsync(sink).ConfigureAwait(false);\n            await d.AssignAsync(inner).ConfigureAwait(false);\n\n            return subscription;\n        }","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupBy.cs#L244-L280","documentation":"GroupBy's comparer overload requires a non-null IEqualityComparer<TKey> to decide group membership; passing null makes key equality undefined, so the operator throws ArgumentNullException eagerly. Note the default comparer is used automatically when no comparer is supplied.","triggerScenarios":"Calling AsyncObservable.GroupBy(source, keySelector, elementSelector, capacity, comparer) with comparer == null — e.g. a comparer field/property that was never initialized or a method parameter forwarded as null.","commonSituations":"DI-resolved or configurable comparer is null; a helper method has a nullable comparer parameter and forwards it directly instead of falling back to EqualityComparer<TKey>.Default.","solutions":["Pass a concrete comparer such as EqualityComparer<TKey>.Default or StringComparer.OrdinalIgnoreCase.","Coalesce before calling: comparer ?? EqualityComparer<TKey>.Default.","Use a GroupBy overload without a comparer to get the default automatically."],"exampleFix":"// before\nvar grouped = source.GroupBy(x => x.Key, x => x, 16, maybeComparer);\n// after\nvar grouped = source.GroupBy(x => x.Key, x => x, 16, maybeComparer ?? EqualityComparer<TKey>.Default);","handlingStrategy":"validation","validationCode":"comparer ??= EqualityComparer<TKey>.Default;\nvar grouped = source.GroupBy(keySelector, elementSelector, capacity, comparer);","typeGuard":"bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;","tryCatchPattern":"try\n{\n    grouped = source.GroupBy(keySelector, elementSelector, capacity, comparer);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == \"comparer\")\n{\n    grouped = source.GroupBy(keySelector, elementSelector); // default comparer\n}","preventionTips":["Coalesce optional comparers to EqualityComparer<TKey>.Default at the call site.","Mark comparer parameters non-nullable unless a default is genuinely intended.","Ensure DI-registered comparer singletons are configured before use."],"tags":["argument-null","comparer","groupby","asyncrx"],"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"}