{"record":{"id":"e3cdd1415d907c5e","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-keyselector-groupbyuntil","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'keySelector')","messagePattern":"Value cannot be null\\. \\(Parameter 'keySelector'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":36,"sourceCode":"            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\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)","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L18-L54","documentation":"GroupByUntil requires a keySelector delegate that maps each source element to a group key. The operator eagerly validates arguments when the overload with (source, keySelector, durationSelector, comparer) is called, and throws ArgumentNullException when keySelector is null. This fail-fast check prevents a null delegate from surfacing later inside the subscription pipeline.","triggerScenarios":"Calling AsyncObservable.GroupByUntil(source, null, durationSelector, comparer) with the 4-argument overload that accepts an IEqualityComparer<TKey>, passing null for the keySelector parameter.","commonSituations":"Storing the key selector in a variable that was never assigned (e.g. a nullable Func field defaulting to null), conditional delegation where a lambda is only set in some code paths, or refactoring that removed the lambda but left the call site intact.","solutions":["Pass a non-null lambda as keySelector, e.g. x => x.Category","Check the variable supplying the delegate for null before calling GroupByUntil","If the key selector is optional by design, supply a default like _ => default(TKey) instead of null"],"exampleFix":"// before\nFunc<Order, string> keySel = config.Mode == \"cat\" ? (Func<Order,string>)null : o => o.Id;\nvar groups = source.GroupByUntil(keySel, g => Observable.Never<Unit>(), comparer);\n// after\nFunc<Order, string> keySel = config.Mode == \"cat\" ? (Func<Order,string>)(o => o.Category) : o => o.Id;\nvar groups = source.GroupByUntil(keySel, g => Observable.Never<Unit>(), comparer);","handlingStrategy":"validation","validationCode":"if (keySelector == null) throw new ArgumentException(\"keySelector must be provided before calling GroupByUntil\");","typeGuard":"bool HasKeySelector<TSource,TKey>(Func<TSource,TKey> f) => f is not null;","tryCatchPattern":"try { var groups = source.GroupByUntil(ks, ds, cmp); } catch (ArgumentNullException ex) when (ex.ParamName == \"keySelector\") { /* supply default selector or fail fast */ }","preventionTips":["Never store delegates in nullable fields without an initializer","Prefer method groups or lambdas inline over nullable Func variables","Enable nullable reference types (Func<TSource,TKey>?) makes null-flow visible at compile time"],"tags":["argument-null","linq","async-rx","validation"],"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"}