{"record":{"id":"b12b8ac04501d1b5","repo":"dotnet/reactive","slug":"durationselector","errorCode":null,"errorMessage":"durationSelector","messagePattern":"durationSelector","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":199,"sourceCode":"            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, ValueTask<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)\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n\n            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(\n                source,\n                (keySelector, durationSelector, capacity),\n                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector, state.capacity)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupByUntil<TSource, TKey, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, 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 (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (capacity < 0)","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L181-L217","documentation":"This ArgumentNullException is thrown synchronously when the durationSelector argument is null. durationSelector decides how long each group lives (it returns the observable whose completion closes the group), so the operator cannot function without it and rejects null immediately at call time.","triggerScenarios":"Calling GroupByUntil with a valid keySelector but null durationSelector, e.g. source.GroupByUntil(keySelector, null) or source.GroupByUntil(keySelector, null, capacity).","commonSituations":"Overload resolution confusion when adding a capacity/comparer argument shifts positional parameters and the duration lambda slot ends up null; a computed selector variable left uninitialized.","solutions":["Provide a duration selector, e.g. g => g.TakeUntil(timerObservable).","If group lifetime should be unbounded, use GroupBy instead of passing a null duration selector.","Null-check any dynamically built selector before invoking GroupByUntil."],"exampleFix":"// before\nvar grouped = source.GroupByUntil(x => ValueTask.FromResult(x.Id), (Func<IGroupedAsyncObservable<int, T>, IAsyncObservable<Unit>>)null);\n// after\nvar grouped = source.GroupByUntil(\n    x => ValueTask.FromResult(x.Id),\n    g => AsyncObservable.Timer(TimeSpan.FromMinutes(5)).Select(_ => Unit.Default));","handlingStrategy":"validation","validationCode":"if (durationSelector is null)\n{\n    durationSelector = g => AsyncObservable.Timer(TimeSpan.FromMinutes(5)).Select(_ => Unit.Default);\n}","typeGuard":"static bool IsValidSelector<TG, TD>(Func<TG, IAsyncObservable<TD>>? f) => f is not null;","tryCatchPattern":"try\n{\n    var grouped = source.GroupByUntil(keySelector, durationSelector);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == \"durationSelector\")\n{\n    // build a default duration selector or report misconfiguration\n}","preventionTips":["Use GroupBy when group lifetime should be unbounded instead of passing null.","Centralize default duration selectors as reusable helper methods.","Keep long overload calls on one clearly commented line so positional nulls are obvious."],"tags":["argument-null","linq","async-reactive"],"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"}