{"record":{"id":"5ced6adb17451cf1","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-comparer-distinctuntilchanged","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'comparer')","messagePattern":"Value cannot be null\\. \\(Parameter 'comparer'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"Ix.NET/Source/System.Interactive/System/Linq/Operators/DistinctUntilChanged.cs","lineNumber":37,"sourceCode":"            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n\n            return DistinctUntilChangedCore(source, x => x, EqualityComparer<TSource>.Default);\n        }\n\n        /// <summary>\n        /// Returns consecutive distinct elements by using the specified equality comparer to compare values.\n        /// </summary>\n        /// <typeparam name=\"TSource\">Source sequence element type.</typeparam>\n        /// <param name=\"source\">Source sequence.</param>\n        /// <param name=\"comparer\">Comparer used to compare values.</param>\n        /// <returns>Sequence without adjacent non-distinct elements.</returns>\n        public static IEnumerable<TSource> DistinctUntilChanged<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)\n        {\n            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));\n\n            return DistinctUntilChangedCore(source, x => x, comparer);\n        }\n\n        /// <summary>\n        /// Returns consecutive distinct elements based on a key value by using the specified equality comparer to compare key values.\n        /// </summary>\n        /// <typeparam name=\"TSource\">Source sequence element type.</typeparam>\n        /// <typeparam name=\"TKey\">Key type.</typeparam>\n        /// <param name=\"source\">Source sequence.</param>\n        /// <param name=\"keySelector\">Key selector.</param>\n        /// <returns>Sequence without adjacent non-distinct elements.</returns>\n        public static IEnumerable<TSource> DistinctUntilChanged<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)\n        {\n            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Ix.NET/Source/System.Interactive/System/Linq/Operators/DistinctUntilChanged.cs#L19-L55","documentation":"System.Interactive's DistinctUntilChanged(source, comparer) eagerly validates arguments at call time (not enumeration time). When the IEqualityComparer<TSource> passed as 'comparer' is null, it throws ArgumentNullException immediately. This fail-fast pattern ensures the error points at the buggy call site rather than a later InvalidOperationException during MoveNext.","triggerScenarios":"Calling EnumerableEx.DistinctUntilChanged(source, comparer) with a null second argument, typically because the comparer came from a variable/parameter/lookup that was null (e.g. a static field not yet initialized, a config-resolved comparer, or passing null intending 'default comparer').","commonSituations":"Developers porting from Distinct() which accepts an implicit default comparer, DI-resolved comparer services that failed to register, refactoring where a comparer factory returns null, or calling the 2-arg overload believing it takes a keySelector (it takes a comparer, so null slips through intent).","solutions":["Pass an actual comparer: DistinctUntilChanged(source, EqualityComparer<TSource>.Default) if you wanted default semantics.","If you have no comparer, call the 1-argument overload DistinctUntilChanged(source) which uses the default element comparer.","Null-check or ?? the comparer at the call site: comparer ?? EqualityComparer<TSource>.Default.","Fix the upstream producer (factory, DI container registration, config) so it does not return null comparers."],"exampleFix":"// before\nvar comparer = _comparerRegistry[type]; // null when unregistered\nvar result = source.DistinctUntilChanged(comparer);\n// after\nvar comparer = _comparerRegistry[type] ?? EqualityComparer<MyType>.Default;\nvar result = source.DistinctUntilChanged(comparer);","handlingStrategy":"validation","validationCode":"if (source is null) throw new ArgumentNullException(nameof(source));\nif (comparer is null) comparer = EqualityComparer<TSource>.Default; // or reject with a clear message\nvar result = source.DistinctUntilChanged(comparer);","typeGuard":"static bool HasComparer<TSource>(IEqualityComparer<TSource>? c) => c is not null;","tryCatchPattern":"try\n{\n    var result = source.DistinctUntilChanged(comparer);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == \"comparer\")\n{\n    // fall back to default comparer\n    result = source.DistinctUntilChanged(EqualityComparer<TSource>.Default);\n}","preventionTips":["Prefer the 1- or 2-argument DistinctUntilChanged overloads when default comparison suffices","Coalesce optional comparers with ?? EqualityComparer<T>.Default at the call site","Never forward optional comparer parameters without a default","Make DI comparer registrations mandatory so the container fails at startup, not at pipeline build"],"tags":["argumentnull","linq","ix-net","null-check"],"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"}