diff --git a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs index 692f4bc23ac5df..bd3497d53c6580 100644 --- a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs +++ b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs @@ -86,7 +86,9 @@ public static partial class Queryable public static System.Linq.IQueryable Except(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2) { throw null; } public static System.Linq.IQueryable Except(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? FirstOrDefault(this System.Linq.IQueryable source) { throw null; } + public static TSource FirstOrDefault(this System.Linq.IQueryable source, TSource defaultValue) { throw null; } public static TSource? FirstOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } + public static TSource FirstOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate, TSource defaultValue) { throw null; } public static TSource First(this System.Linq.IQueryable source) { throw null; } public static TSource First(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static System.Linq.IQueryable> GroupBy(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> keySelector) { throw null; } @@ -104,7 +106,9 @@ public static partial class Queryable public static System.Linq.IQueryable Join(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector) { throw null; } public static System.Linq.IQueryable Join(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? LastOrDefault(this System.Linq.IQueryable source) { throw null; } + public static TSource LastOrDefault(this System.Linq.IQueryable source, TSource defaultValue) { throw null; } public static TSource? LastOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } + public static TSource LastOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate, TSource defaultValue) { throw null; } public static TSource Last(this System.Linq.IQueryable source) { throw null; } public static TSource Last(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static long LongCount(this System.Linq.IQueryable source) { throw null; } @@ -129,7 +133,9 @@ public static partial class Queryable public static bool SequenceEqual(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2) { throw null; } public static bool SequenceEqual(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? SingleOrDefault(this System.Linq.IQueryable source) { throw null; } + public static TSource SingleOrDefault(this System.Linq.IQueryable source, TSource defaultValue) { throw null; } public static TSource? SingleOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } + public static TSource SingleOrDefault(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate, TSource defaultValue) { throw null; } public static TSource Single(this System.Linq.IQueryable source) { throw null; } public static TSource Single(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static System.Linq.IQueryable SkipLast(this System.Linq.IQueryable source, int count) { throw null; } diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs index f0eb0f169bf3c0..be11c326812cb5 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs @@ -284,6 +284,20 @@ public static MethodInfo FirstOrDefault_TSource_2(Type TSource) => (s_FirstOrDefault_TSource_2 ??= new Func, Expression>, object?>(Queryable.FirstOrDefault).GetMethodInfo().GetGenericMethodDefinition()) .MakeGenericMethod(TSource); + private static MethodInfo? s_FirstOrDefault_TSource_3; + + public static MethodInfo FirstOrDefault_TSource_3(Type TSource) => + (s_FirstOrDefault_TSource_3 ?? + (s_FirstOrDefault_TSource_3 = new Func, object, object>(Queryable.FirstOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + + private static MethodInfo? s_FirstOrDefault_TSource_4; + + public static MethodInfo FirstOrDefault_TSource_4(Type TSource) => + (s_FirstOrDefault_TSource_4 ?? + (s_FirstOrDefault_TSource_4 = new Func, Expression>, object, object>(Queryable.FirstOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + private static MethodInfo? s_GroupBy_TSource_TKey_2; public static MethodInfo GroupBy_TSource_TKey_2(Type TSource, Type TKey) => @@ -392,6 +406,20 @@ public static MethodInfo LastOrDefault_TSource_2(Type TSource) => (s_LastOrDefault_TSource_2 ??= new Func, Expression>, object?>(Queryable.LastOrDefault).GetMethodInfo().GetGenericMethodDefinition()) .MakeGenericMethod(TSource); + private static MethodInfo? s_LastOrDefault_TSource_3; + + public static MethodInfo LastOrDefault_TSource_3(Type TSource) => + (s_LastOrDefault_TSource_3 ?? + (s_LastOrDefault_TSource_3 = new Func, object, object>(Queryable.LastOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + + private static MethodInfo? s_LastOrDefault_TSource_4; + + public static MethodInfo LastOrDefault_TSource_4(Type TSource) => + (s_LastOrDefault_TSource_4 ?? + (s_LastOrDefault_TSource_4 = new Func, Expression>, object, object>(Queryable.LastOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + private static MethodInfo? s_LongCount_TSource_1; public static MethodInfo LongCount_TSource_1(Type TSource) => @@ -536,6 +564,20 @@ public static MethodInfo SingleOrDefault_TSource_2(Type TSource) => (s_SingleOrDefault_TSource_2 ??= new Func, Expression>, object?>(Queryable.SingleOrDefault).GetMethodInfo().GetGenericMethodDefinition()) .MakeGenericMethod(TSource); + private static MethodInfo? s_SingleOrDefault_TSource_3; + + public static MethodInfo SingleOrDefault_TSource_3(Type TSource) => + (s_SingleOrDefault_TSource_3 ?? + (s_SingleOrDefault_TSource_3 = new Func, object, object>(Queryable.SingleOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + + private static MethodInfo? s_SingleOrDefault_TSource_4; + + public static MethodInfo SingleOrDefault_TSource_4(Type TSource) => + (s_SingleOrDefault_TSource_4 ?? + (s_SingleOrDefault_TSource_4 = new Func, Expression>, object, object>(Queryable.SingleOrDefault).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TSource); + private static MethodInfo? s_Skip_TSource_2; public static MethodInfo Skip_TSource_2(Type TSource) => diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs index c08bcab79af79c..36a7789d464f84 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -864,6 +864,18 @@ public static TSource First(this IQueryable source, Expression CachedReflectionInfo.FirstOrDefault_TSource_1(typeof(TSource)), source.Expression)); } + [DynamicDependency("FirstOrDefault`1", typeof(Enumerable))] + public static TSource FirstOrDefault(this IQueryable source, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.FirstOrDefault_TSource_3(typeof(TSource)), + source.Expression, Expression.Constant(defaultValue, typeof(TSource)))); + } + [DynamicDependency("FirstOrDefault`1", typeof(Enumerable))] public static TSource? FirstOrDefault(this IQueryable source, Expression> predicate) { @@ -879,6 +891,21 @@ public static TSource First(this IQueryable source, Expression )); } + [DynamicDependency("FirstOrDefault`1", typeof(Enumerable))] + public static TSource FirstOrDefault(this IQueryable source, Expression> predicate, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + if (predicate == null) + throw Error.ArgumentNull(nameof(predicate)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.FirstOrDefault_TSource_4(typeof(TSource)), + source.Expression, Expression.Quote(predicate), Expression.Constant(defaultValue, typeof(TSource)) + )); + } + [DynamicDependency("Last`1", typeof(Enumerable))] public static TSource Last(this IQueryable source) { @@ -916,6 +943,18 @@ public static TSource Last(this IQueryable source, Expression< CachedReflectionInfo.LastOrDefault_TSource_1(typeof(TSource)), source.Expression)); } + [DynamicDependency("LastOrDefault`1", typeof(Enumerable))] + public static TSource LastOrDefault(this IQueryable source, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.LastOrDefault_TSource_3(typeof(TSource)), + source.Expression, Expression.Constant(defaultValue, typeof(TSource)))); + } + [DynamicDependency("LastOrDefault`1", typeof(Enumerable))] public static TSource? LastOrDefault(this IQueryable source, Expression> predicate) { @@ -931,6 +970,21 @@ public static TSource Last(this IQueryable source, Expression< )); } + [DynamicDependency("LastOrDefault`1", typeof(Enumerable))] + public static TSource LastOrDefault(this IQueryable source, Expression> predicate, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + if (predicate == null) + throw Error.ArgumentNull(nameof(predicate)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.LastOrDefault_TSource_4(typeof(TSource)), + source.Expression, Expression.Quote(predicate), Expression.Constant(defaultValue, typeof(TSource)) + )); + } + [DynamicDependency("Single`1", typeof(Enumerable))] public static TSource Single(this IQueryable source) { @@ -968,6 +1022,19 @@ public static TSource Single(this IQueryable source, Expressio CachedReflectionInfo.SingleOrDefault_TSource_1(typeof(TSource)), source.Expression)); } + [DynamicDependency("SingleOrDefault`1", typeof(Enumerable))] + public static TSource SingleOrDefault(this IQueryable source, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.SingleOrDefault_TSource_3(typeof(TSource)), + source.Expression, Expression.Constant(defaultValue, typeof(TSource)))); + + } + [DynamicDependency("SingleOrDefault`1", typeof(Enumerable))] public static TSource? SingleOrDefault(this IQueryable source, Expression> predicate) { @@ -983,6 +1050,21 @@ public static TSource Single(this IQueryable source, Expressio )); } + [DynamicDependency("SingleOrDefault`1", typeof(Enumerable))] + public static TSource SingleOrDefault(this IQueryable source, Expression> predicate, TSource defaultValue) + { + if (source == null) + throw Error.ArgumentNull(nameof(source)); + if (predicate == null) + throw Error.ArgumentNull(nameof(predicate)); + return source.Provider.Execute( + Expression.Call( + null, + CachedReflectionInfo.SingleOrDefault_TSource_4(typeof(TSource)), + source.Expression, Expression.Quote(predicate), Expression.Constant(defaultValue, typeof(TSource)) + )); + } + [DynamicDependency("ElementAt`1", typeof(Enumerable))] public static TSource ElementAt(this IQueryable source, int index) { diff --git a/src/libraries/System.Linq.Queryable/tests/FirstOrDefaultTests.cs b/src/libraries/System.Linq.Queryable/tests/FirstOrDefaultTests.cs index 95093c0e8596af..ac74ca5bd68365 100644 --- a/src/libraries/System.Linq.Queryable/tests/FirstOrDefaultTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/FirstOrDefaultTests.cs @@ -16,6 +16,15 @@ public void Empty() Assert.Equal(0, source.AsQueryable().FirstOrDefault()); } + [Fact] + public void EmptyDefault() + { + int[] source = { }; + int defaultValue = 5; + + Assert.Equal(defaultValue, source.AsQueryable().FirstOrDefault(defaultValue)); + } + [Fact] public void ManyElementsFirstIsDefault() { @@ -37,6 +46,27 @@ public void OneElementTruePredicate() Assert.Equal(4, source.AsQueryable().FirstOrDefault(i => i % 2 == 0)); } + [Fact] + public void OneElementTruePredicateDefault() + { + int[] source = { 4 }; + Assert.Equal(4, source.AsQueryable().FirstOrDefault(i => i % 2 == 0, 5)); + } + + [Fact] + public void OneElementFalsePredicate() + { + int[] source = { 3 }; + Assert.Equal(0, source.AsQueryable().FirstOrDefault(i => i % 2 == 0)); + } + + [Fact] + public void OneElementFalsePredicateDefault() + { + int[] source = { 3 }; + Assert.Equal(5, source.AsQueryable().FirstOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ManyElementsPredicateFalseForAll() { @@ -44,23 +74,38 @@ public void ManyElementsPredicateFalseForAll() Assert.Equal(0, source.AsQueryable().FirstOrDefault(i => i % 2 == 0)); } + [Fact] + public void ManyElementsPredicateFalseForAllDefault() + { + int[] source = { 9, 5, 1, 3, 17, 21 }; + Assert.Equal(2, source.AsQueryable().FirstOrDefault(i => i % 2 == 0, 2)); + } + [Fact] public void PredicateTrueForSome() { int[] source = { 3, 7, 10, 7, 9, 2, 11, 17, 13, 8 }; Assert.Equal(10, source.AsQueryable().FirstOrDefault(i => i % 2 == 0)); } + [Fact] + public void PredicateTrueForSomeDefault() + { + int[] source = { 3, 7, 10, 7, 9, 2, 11, 17, 13, 8 }; + Assert.Equal(10, source.AsQueryable().FirstOrDefault(i => i % 2 == 0, 5)); + } [Fact] public void NullSource() { AssertExtensions.Throws("source", () => ((IQueryable)null).FirstOrDefault()); + AssertExtensions.Throws("source", () => ((IQueryable)null).FirstOrDefault(5)); } [Fact] public void NullSourcePredicateUsed() { AssertExtensions.Throws("source", () => ((IQueryable)null).FirstOrDefault(i => i != 2)); + AssertExtensions.Throws("source", () => ((IQueryable)null).FirstOrDefault(i => i != 2, 5)); } [Fact] @@ -68,6 +113,7 @@ public void NullPredicate() { Expression> predicate = null; AssertExtensions.Throws("predicate", () => Enumerable.Range(0, 3).AsQueryable().FirstOrDefault(predicate)); + AssertExtensions.Throws("predicate", () => Enumerable.Range(0, 3).AsQueryable().FirstOrDefault(predicate, 5)); } [Fact] diff --git a/src/libraries/System.Linq.Queryable/tests/LastOrDefaultTests.cs b/src/libraries/System.Linq.Queryable/tests/LastOrDefaultTests.cs index 62bfe0a9d29f2c..c66c42fce4a4fe 100644 --- a/src/libraries/System.Linq.Queryable/tests/LastOrDefaultTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/LastOrDefaultTests.cs @@ -14,6 +14,14 @@ public void Empty() Assert.Null(Enumerable.Empty().AsQueryable().LastOrDefault()); } + [Fact] + public void EmptyDefault() + { + int[] source = { }; + int defaultValue = 5; + Assert.Equal(defaultValue, source.AsQueryable().LastOrDefault(defaultValue)); + } + [Fact] public void OneElement() { @@ -21,6 +29,13 @@ public void OneElement() Assert.Equal(5, source.AsQueryable().LastOrDefault()); } + [Fact] + public void OneElementFalsePredicate() + { + int[] source = { 3 }; + Assert.Equal(5, source.AsQueryable().LastOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ManyElementsLastIsDefault() { diff --git a/src/libraries/System.Linq.Queryable/tests/SingleOrDefaultTests.cs b/src/libraries/System.Linq.Queryable/tests/SingleOrDefaultTests.cs index 032ca44ba83ce5..f34edae8b9f84d 100644 --- a/src/libraries/System.Linq.Queryable/tests/SingleOrDefaultTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/SingleOrDefaultTests.cs @@ -22,12 +22,26 @@ public void Empty() Assert.Null(Enumerable.Empty().AsQueryable().SingleOrDefault()); } + [Fact] + public void EmptyDefault() + { + int[] source = { }; + int defaultValue = 5; + Assert.Equal(defaultValue, source.AsQueryable().SingleOrDefault(5)); + } + [Fact] public void EmptySourceWithPredicate() { Assert.Null(Enumerable.Empty().AsQueryable().SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void EmptySourceWithPredicateDefault() + { + Assert.Equal(5, Enumerable.Empty().AsQueryable().SingleOrDefault(i => i % 2 == 0, 5)); + } + [Theory] [InlineData(1, 100)] [InlineData(42, 100)] diff --git a/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs b/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs index 3d02b20ae0ec13..b1463804c69086 100644 --- a/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/TrimCompatibilityTests.cs @@ -61,7 +61,7 @@ public static void CachedReflectionInfoMethodsNoAnnotations() .Where(m => m.GetParameters().Length > 0); // If you are adding a new method to this class, ensure the method meets these requirements - Assert.Equal(111, methods.Count()); + Assert.Equal(117, methods.Count()); foreach (MethodInfo method in methods) { ParameterInfo[] parameters = method.GetParameters(); diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index 7a86417711aa74..0f09240a0249de 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -59,7 +59,9 @@ public static System.Collections.Generic.IEnumerable< public static System.Collections.Generic.IEnumerable Except(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second) { throw null; } public static System.Collections.Generic.IEnumerable Except(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? FirstOrDefault(this System.Collections.Generic.IEnumerable source) { throw null; } + public static TSource FirstOrDefault(this System.Collections.Generic.IEnumerable source, TSource defaultValue) { throw null; } public static TSource? FirstOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } + public static TSource FirstOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate, TSource defaultValue) { throw null; } public static TSource First(this System.Collections.Generic.IEnumerable source) { throw null; } public static TSource First(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static System.Collections.Generic.IEnumerable> GroupBy(this System.Collections.Generic.IEnumerable source, System.Func keySelector) { throw null; } @@ -77,7 +79,9 @@ public static System.Collections.Generic.IEnumerable< public static System.Collections.Generic.IEnumerable Join(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector) { throw null; } public static System.Collections.Generic.IEnumerable Join(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? LastOrDefault(this System.Collections.Generic.IEnumerable source) { throw null; } + public static TSource LastOrDefault(this System.Collections.Generic.IEnumerable source, TSource defaultValue) { throw null; } public static TSource? LastOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } + public static TSource LastOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate, TSource defaultValue) { throw null; } public static TSource Last(this System.Collections.Generic.IEnumerable source) { throw null; } public static TSource Last(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static long LongCount(this System.Collections.Generic.IEnumerable source) { throw null; } @@ -144,7 +148,9 @@ public static System.Collections.Generic.IEnumerable< public static bool SequenceEqual(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second) { throw null; } public static bool SequenceEqual(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static TSource? SingleOrDefault(this System.Collections.Generic.IEnumerable source) { throw null; } + public static TSource SingleOrDefault(this System.Collections.Generic.IEnumerable source, TSource defaultValue) { throw null; } public static TSource? SingleOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } + public static TSource SingleOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate, TSource defaultValue) { throw null; } public static TSource Single(this System.Collections.Generic.IEnumerable source) { throw null; } public static TSource Single(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static System.Collections.Generic.IEnumerable SkipLast(this System.Collections.Generic.IEnumerable source, int count) { throw null; } diff --git a/src/libraries/System.Linq/src/System/Linq/First.cs b/src/libraries/System.Linq/src/System/Linq/First.cs index 64b89915c259f8..7f59d1c63b5872 100644 --- a/src/libraries/System.Linq/src/System/Linq/First.cs +++ b/src/libraries/System.Linq/src/System/Linq/First.cs @@ -31,10 +31,23 @@ public static TSource First(this IEnumerable source, Func(this IEnumerable source) => - source.TryGetFirst(out bool _); + source.TryGetFirst(out _); + + public static TSource FirstOrDefault(this IEnumerable source, TSource defaultValue) + { + TSource? first = source.TryGetFirst(out bool found); + return found ? first! : defaultValue; + } public static TSource? FirstOrDefault(this IEnumerable source, Func predicate) => - source.TryGetFirst(predicate, out bool _); + source.TryGetFirst(predicate, out _); + + public static TSource FirstOrDefault(this IEnumerable source, Func predicate, TSource defaultValue) + { + TSource? first = source.TryGetFirst(predicate, out bool found); + return found ? first! : defaultValue; + } + private static TSource? TryGetFirst(this IEnumerable source, out bool found) { diff --git a/src/libraries/System.Linq/src/System/Linq/Last.cs b/src/libraries/System.Linq/src/System/Linq/Last.cs index f7cd74eb694bd4..16318de2574ccc 100644 --- a/src/libraries/System.Linq/src/System/Linq/Last.cs +++ b/src/libraries/System.Linq/src/System/Linq/Last.cs @@ -31,10 +31,23 @@ public static TSource Last(this IEnumerable source, Func(this IEnumerable source) => - source.TryGetLast(out bool _); + source.TryGetLast(out _); - public static TSource? LastOrDefault(this IEnumerable source, Func predicate) => - source.TryGetLast(predicate, out bool _); + + public static TSource LastOrDefault(this IEnumerable source, TSource defaultValue) + { + TSource? last = source.TryGetLast(out bool found); + return found ? last! : defaultValue; + } + + public static TSource? LastOrDefault(this IEnumerable source, Func predicate) + => source.TryGetLast(predicate, out _); + + public static TSource LastOrDefault(this IEnumerable source, Func predicate, TSource defaultValue) + { + var last = source.TryGetLast(predicate, out bool found); + return found ? last! : defaultValue; + } private static TSource? TryGetLast(this IEnumerable source, out bool found) { diff --git a/src/libraries/System.Linq/src/System/Linq/Single.cs b/src/libraries/System.Linq/src/System/Linq/Single.cs index 913405d502c2f7..6455a841c9c46f 100644 --- a/src/libraries/System.Linq/src/System/Linq/Single.cs +++ b/src/libraries/System.Linq/src/System/Linq/Single.cs @@ -10,82 +10,46 @@ public static partial class Enumerable { public static TSource Single(this IEnumerable source) { - if (source == null) + TSource? single = source.TryGetSingle(out bool found); + if (!found) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); - } - - if (source is IList list) - { - switch (list.Count) - { - case 0: - ThrowHelper.ThrowNoElementsException(); - return default; - case 1: - return list[0]; - } - } - else - { - using (IEnumerator e = source.GetEnumerator()) - { - if (!e.MoveNext()) - { - ThrowHelper.ThrowNoElementsException(); - } - - TSource result = e.Current; - if (!e.MoveNext()) - { - return result; - } - } + ThrowHelper.ThrowNoElementsException(); } - ThrowHelper.ThrowMoreThanOneElementException(); - return default; + return single!; } - public static TSource Single(this IEnumerable source, Func predicate) { - if (source == null) + TSource? single = source.TryGetSingle(predicate, out bool found); + if (!found) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + ThrowHelper.ThrowNoMatchException(); } - if (predicate == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); - } + return single!; + } - using (IEnumerator e = source.GetEnumerator()) - { - while (e.MoveNext()) - { - TSource result = e.Current; - if (predicate(result)) - { - while (e.MoveNext()) - { - if (predicate(e.Current)) - { - ThrowHelper.ThrowMoreThanOneMatchException(); - } - } + public static TSource? SingleOrDefault(this IEnumerable source) + => source.TryGetSingle(out _); - return result; - } - } - } + public static TSource SingleOrDefault(this IEnumerable source, TSource defaultValue) + { + var single = source.TryGetSingle(out bool found); + return found ? single! : defaultValue; + } - ThrowHelper.ThrowNoMatchException(); - return default; + public static TSource? SingleOrDefault(this IEnumerable source, Func predicate) + => source.TryGetSingle(predicate, out _); + + public static TSource SingleOrDefault(this IEnumerable source, Func predicate, TSource defaultValue) + { + var single = source.TryGetSingle(predicate, out bool found); + return found ? single! : defaultValue; } - public static TSource? SingleOrDefault(this IEnumerable source) + private static TSource? TryGetSingle(this IEnumerable source, out bool found) { - if (source == null) + if (source is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } @@ -95,8 +59,10 @@ public static TSource Single(this IEnumerable source, Func(this IEnumerable source, Func(this IEnumerable source, Func predicate) + private static TSource? TryGetSingle(this IEnumerable source, Func predicate, out bool found) { if (source == null) { @@ -147,12 +116,13 @@ public static TSource Single(this IEnumerable source, Func() Assert.Equal(expected, source.RunOnce().FirstOrDefault()); } + private static void TestEmptyIListDefault(T defaultValue) + { + T[] source = { }; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(defaultValue, source.RunOnce().FirstOrDefault(defaultValue)); + } + [Fact] public void EmptyIListT() { @@ -48,6 +57,14 @@ public void EmptyIListT() TestEmptyIList(); } + [Fact] + public void EmptyIListDefault() + { + TestEmptyIListDefault(5); // int + TestEmptyIListDefault("Hello"); // string + TestEmptyIListDefault(DateTime.UnixEpoch); //DateTime + } + [Fact] public void IListTOneElement() { @@ -59,6 +76,17 @@ public void IListTOneElement() Assert.Equal(expected, source.FirstOrDefault()); } + [Fact] + public void IListOneElementDefault() + { + int[] source = { 5 }; + int expected = 5; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(expected, source.FirstOrDefault(3)); + } + [Fact] public void IListTManyElementsFirstIsDefault() { @@ -96,6 +124,20 @@ static IEnumerable EmptySource() Assert.Equal(expected, source.RunOnce().FirstOrDefault()); } + private static void TestEmptyNotIListDefault(T defaultValue) + { + static IEnumerable EmptySource() + { + yield break; + } + + var source = EmptySource(); + + Assert.Null(source as IList); + + Assert.Equal(defaultValue, source.RunOnce().FirstOrDefault(defaultValue)); + } + [Fact] public void EmptyNotIListT() { @@ -146,6 +188,16 @@ public void OneElementTruePredicate() Assert.Equal(expected, source.FirstOrDefault(predicate)); } + [Fact] + public void OneElementTruePredicateDefault() + { + int[] source = { 4 }; + Func predicate = IsEven; + int expected = 4; + + Assert.Equal(expected, source.FirstOrDefault(predicate, 5)); + } + [Fact] public void ManyElementsPredicateFalseForAll() { @@ -156,6 +208,16 @@ public void ManyElementsPredicateFalseForAll() Assert.Equal(expected, source.FirstOrDefault(predicate)); } + [Fact] + public void ManyElementsPredicateFalseForAllDefault() + { + int[] source = { 9, 5, 1, 3, 17, 21 }; + Func predicate = IsEven; + int expected = 5; + + Assert.Equal(expected, source.FirstOrDefault(predicate, 5)); + } + [Fact] public void PredicateTrueOnlyForLast() { @@ -166,6 +228,16 @@ public void PredicateTrueOnlyForLast() Assert.Equal(expected, source.FirstOrDefault(predicate)); } + [Fact] + public void PredicateTrueOnlyForLastDefault() + { + int[] source = { 9, 5, 1, 3, 17, 21, 50 }; + Func predicate = IsEven; + int expected = 50; + + Assert.Equal(expected, source.FirstOrDefault(predicate, 5)); + } + [Fact] public void PredicateTrueForSome() { @@ -176,6 +248,16 @@ public void PredicateTrueForSome() Assert.Equal(expected, source.FirstOrDefault(predicate)); } + [Fact] + public void PredicateTrueForSomeDefault() + { + int[] source = { 3, 7, 10, 7, 9, 2, 11, 17, 13, 8 }; + Func predicate = IsEven; + int expected = 10; + + Assert.Equal(expected, source.FirstOrDefault(predicate, 5)); + } + [Fact] public void PredicateTrueForSomeRunOnce() { @@ -190,12 +272,14 @@ public void PredicateTrueForSomeRunOnce() public void NullSource() { AssertExtensions.Throws("source", () => ((IEnumerable)null).FirstOrDefault()); + AssertExtensions.Throws("source", () => ((IEnumerable)null).FirstOrDefault(5)); } [Fact] public void NullSourcePredicateUsed() { AssertExtensions.Throws("source", () => ((IEnumerable)null).FirstOrDefault(i => i != 2)); + AssertExtensions.Throws("source", () => ((IEnumerable)null).FirstOrDefault(i => i != 2, 5)); } [Fact] @@ -203,6 +287,7 @@ public void NullPredicate() { Func predicate = null; AssertExtensions.Throws("predicate", () => Enumerable.Range(0, 3).FirstOrDefault(predicate)); + AssertExtensions.Throws("predicate", () => Enumerable.Range(0, 3).FirstOrDefault(predicate, 5)); } } } diff --git a/src/libraries/System.Linq/tests/LastOrDefaultTests.cs b/src/libraries/System.Linq/tests/LastOrDefaultTests.cs index ec8d6c40b62448..ab3a3fa1ad8a1b 100644 --- a/src/libraries/System.Linq/tests/LastOrDefaultTests.cs +++ b/src/libraries/System.Linq/tests/LastOrDefaultTests.cs @@ -39,6 +39,15 @@ private static void TestEmptyIList() Assert.Equal(expected, source.RunOnce().LastOrDefault()); } + private static void TestEmptyIListDefault(T defaultValue) + { + T[] source = { }; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(defaultValue, source.RunOnce().LastOrDefault(defaultValue)); + } + [Fact] public void EmptyIListT() { @@ -48,6 +57,14 @@ public void EmptyIListT() TestEmptyIList(); } + [Fact] + public void EmptyIList() + { + TestEmptyIListDefault(5); // int + TestEmptyIListDefault("Hello"); // string + TestEmptyIListDefault(DateTime.UnixEpoch); + } + [Fact] public void IListTOneElement() { @@ -59,6 +76,17 @@ public void IListTOneElement() Assert.Equal(expected, source.LastOrDefault()); } + [Fact] + public void IListTOneElementDefault() + { + int[] source = { 5 }; + int expected = 5; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(expected, source.LastOrDefault(4)); + } + [Fact] public void IListTManyElementsLastIsDefault() @@ -82,6 +110,28 @@ public void IListTManyElementsLastIsNotDefault() Assert.Equal(expected, source.LastOrDefault()); } + [Fact] + public void IListTManyElementsLastHasDefault() + { + int?[] source = { -10, 2, 4, 3, 0, 2, null }; + int? expected = null; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(expected, source.LastOrDefault(5)); + } + + [Fact] + public void IListTManyElementsLastIsHasDefault() + { + int?[] source = { -10, 2, 4, 3, 0, 2, null, 19 }; + int? expected = 19; + + Assert.IsAssignableFrom>(source); + + Assert.Equal(expected, source.LastOrDefault(5)); + } + private static IEnumerable EmptySource() { yield break; @@ -147,6 +197,16 @@ public void OneElementIListTruePredicate() Assert.Equal(expected, source.LastOrDefault(predicate)); } + [Fact] + public void OneElementIListTruePredicateDefault() + { + int[] source = { 4 }; + Func predicate = IsEven; + int expected = 4; + + Assert.Equal(expected, source.LastOrDefault(predicate, 5)); + } + [Fact] public void ManyElementsIListPredicateFalseForAll() { @@ -157,6 +217,16 @@ public void ManyElementsIListPredicateFalseForAll() Assert.Equal(expected, source.LastOrDefault(predicate)); } + [Fact] + public void ManyElementsIListPredicateFalseForAllDefault() + { + int[] source = { 9, 5, 1, 3, 17, 21 }; + Func predicate = IsEven; + int expected = 5; + + Assert.Equal(expected, source.LastOrDefault(predicate, 5)); + } + [Fact] public void IListPredicateTrueOnlyForLast() { diff --git a/src/libraries/System.Linq/tests/SingleOrDefaultTests.cs b/src/libraries/System.Linq/tests/SingleOrDefaultTests.cs index 1dfca02713808e..9ac26e76a8fa23 100644 --- a/src/libraries/System.Linq/tests/SingleOrDefaultTests.cs +++ b/src/libraries/System.Linq/tests/SingleOrDefaultTests.cs @@ -36,6 +36,15 @@ public void EmptyIList() Assert.Equal(expected, source.SingleOrDefault()); } + [Fact] + public void EmptyIListDefault() + { + int?[] source = { }; + int expected = 5; + + Assert.Equal(expected, source.SingleOrDefault(5)); + } + [Fact] public void SingleElementIList() { @@ -45,6 +54,15 @@ public void SingleElementIList() Assert.Equal(expected, source.SingleOrDefault()); } + [Fact] + public void SingleElementIListDefault() + { + int[] source = { 4 }; + int expected = 4; + + Assert.Equal(expected, source.SingleOrDefault(5)); + } + [Fact] public void ManyElementIList() { @@ -53,6 +71,14 @@ public void ManyElementIList() Assert.Throws(() => source.SingleOrDefault()); } + [Fact] + public void ManyElementIListDefault() + { + int[] source = { 4, 4, 4, 4, 4 }; + + Assert.Throws(() => source.SingleOrDefault(5)); + } + [Fact] public void EmptyNotIList() { @@ -88,6 +114,15 @@ public void EmptySourceWithPredicate() Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void EmptySourceWithPredicateDefault() + { + int[] source = { }; + int expected = 5; + + Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void SingleElementPredicateTrue() { @@ -97,6 +132,15 @@ public void SingleElementPredicateTrue() Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void SingleElementPredicateTrueDefault() + { + int[] source = { 4 }; + int expected = 4; + + Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void SingleElementPredicateFalse() { @@ -106,6 +150,15 @@ public void SingleElementPredicateFalse() Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void SingleElementPredicateFalseDefault() + { + int[] source = { 3 }; + int expected = 5; + + Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ManyElementsPredicateFalseForAll() { @@ -115,6 +168,15 @@ public void ManyElementsPredicateFalseForAll() Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void ManyElementsPredicateFalseForAllDefault() + { + int[] source = { 3, 1, 7, 9, 13, 19 }; + int expected = 5; + + Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ManyElementsPredicateTrueForLast() { @@ -124,6 +186,15 @@ public void ManyElementsPredicateTrueForLast() Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void ManyElementsPredicateTrueForLastDefault() + { + int[] source = { 3, 1, 7, 9, 13, 19, 20 }; + int expected = 20; + + Assert.Equal(expected, source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ManyElementsPredicateTrueForFirstAndFifth() { @@ -132,6 +203,14 @@ public void ManyElementsPredicateTrueForFirstAndFifth() Assert.Throws(() => source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void ManyElementsPredicateTrueForFirstAndFifthDefault() + { + int[] source = { 2, 3, 1, 7, 10, 13, 19, 9 }; + + Assert.Throws(() => source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Theory] [InlineData(1, 100)] [InlineData(42, 100)] @@ -156,6 +235,14 @@ public void ThrowsOnNullSource() AssertExtensions.Throws("source", () => source.SingleOrDefault(i => i % 2 == 0)); } + [Fact] + public void ThrowsOnNullSourceDefault() + { + int[] source = null; + AssertExtensions.Throws("source", () => source.SingleOrDefault(5)); + AssertExtensions.Throws("source", () => source.SingleOrDefault(i => i % 2 == 0, 5)); + } + [Fact] public void ThrowsOnNullPredicate() { @@ -163,5 +250,13 @@ public void ThrowsOnNullPredicate() Func nullPredicate = null; AssertExtensions.Throws("predicate", () => source.SingleOrDefault(nullPredicate)); } + + [Fact] + public void ThrowsOnNullPredicateDefault() + { + int[] source = { }; + Func nullPredicate = null; + AssertExtensions.Throws("predicate", () => source.SingleOrDefault(nullPredicate, 5)); + } } }