{"record":{"id":"bffddc85a03b9168","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-comparer-bffddc","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'comparer')","messagePattern":"Value cannot be null\\. \\(Parameter 'comparer'\\)","errorType":"exception","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Linq/Observable.StandardSequenceOperators.cs","lineNumber":675,"sourceCode":"\n            if (elementSelector == null)\n            {\n                throw new ArgumentNullException(nameof(elementSelector));\n            }\n\n            if (durationSelector == null)\n            {\n                throw new ArgumentNullException(nameof(durationSelector));\n            }\n\n            if (capacity < 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n            }\n\n            if (comparer == null)\n            {\n                throw new ArgumentNullException(nameof(comparer));\n            }\n\n            return s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer);\n        }\n\n        /// <summary>\n        /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and selects the resulting elements by using a specified function.\n        /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same\n        /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request.\n        /// </summary>\n        /// <typeparam name=\"TSource\">The type of the elements in the source sequence.</typeparam>\n        /// <typeparam name=\"TKey\">The type of the grouping key computed for each element in the source sequence.</typeparam>\n        /// <typeparam name=\"TElement\">The type of the elements within the groups computed for each element in the source sequence.</typeparam>\n        /// <typeparam name=\"TDuration\">The type of the elements in the duration sequences obtained for each group to denote its lifetime.</typeparam>\n        /// <param name=\"source\">An observable sequence whose elements to group.</param>\n        /// <param name=\"keySelector\">A function to extract the key for each element.</param>\n        /// <param name=\"elementSelector\">A function to map each source element to an element in an observable group.</param>\n        /// <param name=\"durationSelector\">A function to signal the expiration of a group.</param>","sourceCodeStart":657,"sourceCodeEnd":693,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Linq/Observable.StandardSequenceOperators.cs#L657-L693","documentation":"System.Reactive's GroupByUntil overload with a capacity and comparer validates its arguments eagerly and throws ArgumentNullException when the IEqualityComparer<TKey> comparer is null. Unlike the plain GroupBy operator, this overload requires an explicit comparer; null is not defaulted to EqualityComparer<TKey>.Default, so the call is rejected before any subscription happens.","triggerScenarios":"Calling Observable.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer) with comparer == null (e.g. passing a comparer variable that was never initialized, or a method parameter forwarded through as null).","commonSituations":"Building a custom key comparison path where the comparer is injected via DI or configuration and the binding silently yields null; refactoring from the comparer-less GroupByUntil overload and passing null explicitly to keep a signature aligned.","solutions":["Pass a concrete comparer, e.g. EqualityComparer<TKey>.Default if default hash/equality semantics are acceptable","Use the GroupByUntil overload without the comparer parameter if default semantics were intended","Ensure the comparer factory/DI binding actually resolves a non-null instance before calling GroupByUntil"],"exampleFix":"// before\nvar result = source.GroupByUntil(x => x.Id, x => x, g => Observable.Timer(TimeSpan.FromMinutes(5)), 64, null);\n// after\nvar result = source.GroupByUntil(x => x.Id, x => x, g => Observable.Timer(TimeSpan.FromMinutes(5)), 64, EqualityComparer<int>.Default);","handlingStrategy":"validation","validationCode":"if (comparer == null) throw new InvalidOperationException(\"GroupByUntil requires a non-null comparer; use EqualityComparer<TKey>.Default or the overload without a comparer.\");","typeGuard":"bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;","tryCatchPattern":"try { var q = source.GroupByUntil(ks, es, ds, cap, comparer); }\ncatch (ArgumentNullException ex) when (ex.ParamName == \"comparer\") { comparer = EqualityComparer<TKey>.Default; /* retry */ }","preventionTips":["Default comparer parameters to EqualityComparer<TKey>.Default instead of null","Never forward possibly-null comparer variables straight into Rx operators","Prefer the comparer-less GroupByUntil overload unless custom equality is required"],"tags":["csharp","rx","argument-null","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"}