{"record":{"id":"b168ad7f99434834","repo":"dotnet/reactive","slug":"elementselector-groupbyuntil","errorCode":null,"errorMessage":"elementSelector","messagePattern":"elementSelector","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":235,"sourceCode":"            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, TSource>>.From(\n                source,\n                (keySelector, durationSelector, capacity, comparer),\n                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector, state.capacity, state.comparer)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupByUntil<TSource, TKey, TElement, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, Func<IGroupedAsyncObservable<TKey, TElement>, IAsyncObservable<TDuration>> durationSelector)\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 (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n\n            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TElement>>.From(\n                source,\n                (keySelector, elementSelector, durationSelector),\n                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TElement, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.elementSelector, state.durationSelector)));\n        }\n\n        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupByUntil<TSource, TKey, TElement, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, Func<IGroupedAsyncObservable<TKey, TElement>, 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 (elementSelector == null)\n                throw new ArgumentNullException(nameof(elementSelector));\n            if (durationSelector == null)","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L217-L253","documentation":"GroupByUntil validates all delegate arguments up-front and throws ArgumentNullException when elementSelector is null. The elementSelector (Func<TSource, ValueTask<TElement>>) is required because it projects each source item into the element exposed on the emitted IGroupedAsyncObservable groups. The library fails fast at subscribe-plan time rather than failing later inside the async pipeline.","triggerScenarios":"Calling any GroupByUntil overload that accepts an elementSelector while passing null for it, e.g. source.GroupByUntil(key, null, durationSelector) or the comparer/capacity variants, in GroupByUntil.cs:235.","commonSituations":"Conditionally-built query pipelines where a projection variable is only assigned on one branch; refactors that removed an element projection but left the call shape; interop code that maps optional delegates from nullable inputs.","solutions":["Pass a non-null elementSelector; if no projection is needed, use GroupBy (key only) or pass an identity selector such as static x => new ValueTask<TElement>(x).","Guard the delegate before calling: if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); or substitute a default.","Check the overload you intend — some overloads require both keySelector and elementSelector; pick the one matching your available delegates."],"exampleFix":"// before\nvar groups = source.GroupByUntil(k => k.Id, null, g => g.Where(x => x.Stale).Take(1));\n// after\nvar groups = source.GroupByUntil(k => k.Id, x => new ValueTask<TElement>(x), g => g.Where(x => x.Stale).Take(1));","handlingStrategy":"validation","validationCode":"if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));\nif (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));\nif (durationSelector == null) throw new ArgumentNullException(nameof(durationSelector));","typeGuard":"bool IsValidSelector<TIn, TOut>(Func<TIn, ValueTask<TOut>> f) => f is not null;","tryCatchPattern":"try { var groups = source.GroupByUntil(key, elem, dur); }\ncatch (ArgumentNullException ex) { logger.LogError(ex, \"GroupByUntil delegate missing: {Param}\", ex.ParamName); }","preventionTips":["Use static lambdas so the compiler forces all arguments to be assigned.","Never default delegate parameters to null in wrapper APIs.","Prefer the simpler GroupBy operator when no element projection is needed."],"tags":["argument-null","linq","async-rx","groupbyuntil"],"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"}