{"record":{"id":"ad62f2123cc933e0","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-durationselector","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'durationSelector')","messagePattern":"Value cannot be null\\. \\(Parameter 'durationSelector'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":38,"sourceCode":"            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)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (capacity < 0)","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L20-L56","documentation":"GroupByUntil needs a durationSelector that maps each created group to an observable signaling when the group closes. In the (source, keySelector, durationSelector, comparer) overload, a null durationSelector throws ArgumentNullException immediately at call time. The check exists so group lifetime logic is always present before the pipeline is built.","triggerScenarios":"Calling AsyncObservable.GroupByUntil(source, keySelector, null, comparer) with the comparer overload, omitting the Func<IGroupedAsyncObservable<TKey,TSource>, IAsyncObservable<TDuration>> argument.","commonSituations":"Porting from Rx.NET GroupBy (which has no duration selector) and dropping the argument, building the delegate via reflection/config that resolved to null, or passing a method group that failed to bind.","solutions":["Supply a duration selector, e.g. g => AsyncObservable.Never<TDuration>() for never-closing groups","Verify the expression producing the duration selector is not null at the call site","Use plain GroupBy instead if group lifetime should be unbounded"],"exampleFix":"// before\nvar groups = source.GroupByUntil(x => x.Key, null, comparer);\n// after\nvar groups = source.GroupByUntil(x => x.Key, g => AsyncObservable.Never<Unit>(), comparer);","handlingStrategy":"validation","validationCode":"if (durationSelector == null) throw new ArgumentException(\"durationSelector is required by GroupByUntil; use AsyncObservable.Never<T>() for unbounded groups\");","typeGuard":"bool HasDurationSelector<TSource,TKey,TD>(Func<IAsyncGroupedObservable<TKey,TSource>,IAsyncObservable<TD>> f) => f is not null;","tryCatchPattern":"try { var groups = source.GroupByUntil(ks, ds, cmp); } catch (ArgumentNullException ex) when (ex.ParamName == \"durationSelector\") { var groups = source.GroupBy(ks, cmp); }","preventionTips":["Remember GroupByUntil always needs a duration selector — there is no overload without one","When porting from GroupBy, map 'no duration' to g => AsyncObservable.Never<Unit>()","Avoid resolving delegates via reflection/config without a null check"],"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"}