diff --git a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs index fbdf9a12a28b6..6b591259337a5 100644 --- a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -63,5 +64,8 @@ internal static bool TryGetSpan(this IEnumerable source, out R return result; } + + [FeatureSwitchDefinition("System.Linq.Enumerable.ValueTypeTrimFriendlySelect")] + internal static bool ValueTypeTrimFriendlySelect { get; } = AppContext.TryGetSwitch("System.Linq.Enumerable.ValueTypeTrimFriendlySelect", out bool isEnabled) ? isEnabled : true; } } diff --git a/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs index 73983cf87fbde..f059542b72bd2 100644 --- a/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs @@ -172,7 +172,7 @@ public override IEnumerable Select(Func s new IEnumerableWhereSelectIterator(objectSource, isTResult, localSelector); } - return base.Select(selector); + return new IteratorSelectIterator(this, selector); } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Select.cs b/src/libraries/System.Linq/src/System/Linq/Select.cs index 37a41c450f873..db9adc9874030 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using static System.Linq.Utilities; namespace System.Linq @@ -25,7 +26,7 @@ public static IEnumerable Select( if (source is Iterator iterator) { - return iterator.Select(selector); + return SelectImplementation(selector, iterator); } if (source is IList ilist) @@ -51,6 +52,26 @@ public static IEnumerable Select( return new IEnumerableSelectIterator(source, selector); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IEnumerable SelectImplementation(Func selector, Iterator iterator) + { + // With native AOT, calling into the `Select` generic virtual method results in NxM + // expansion of native code. If the option is enabled, we don't call the generic virtual + // for value types. We don't do the same for reference types because reference type + // expansion can happen lazily at runtime and the AOT compiler does postpone it (we + // don't need more code, just more data structures describing the new types). + if (ValueTypeTrimFriendlySelect && typeof(TResult).IsValueType) + { +#if OPTIMIZE_FOR_SIZE + return new IEnumerableSelectIterator(iterator, selector); +#else + return new IteratorSelectIterator(iterator, selector); +#endif + } + + return iterator.Select(selector); + } + public static IEnumerable Select(this IEnumerable source, Func selector) { if (source is null) diff --git a/src/libraries/System.Linq/tests/SkipWhileTests.cs b/src/libraries/System.Linq/tests/SkipWhileTests.cs index a13a686cef3f3..5884d9c7ce3c1 100644 --- a/src/libraries/System.Linq/tests/SkipWhileTests.cs +++ b/src/libraries/System.Linq/tests/SkipWhileTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Linq.Tests @@ -62,14 +63,33 @@ public void RunOnce() Assert.Equal(Enumerable.Range(10, 10), Enumerable.Range(0, 20).RunOnce().SkipWhile((i, idx) => idx < 10)); } - [Fact] - public void SkipErrorWhenSourceErrors() + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void SkipErrorWhenSourceErrors_TrimFriendlySelectTrue() { - var source = NumberRangeGuaranteedNotCollectionType(-2, 5).Select(i => (decimal)i).Select(m => 1 / m).Skip(4); - using(var en = source.GetEnumerator()) + RemoteExecutor.Invoke(() => { - Assert.Throws(() => en.MoveNext()); - } + AppContext.SetSwitch("System.Linq.Enumerable.ValueTypeTrimFriendlySelect", true); + + var source = NumberRangeGuaranteedNotCollectionType(-2, 5).Select(i => (decimal)i).Select(m => 1 / m).Skip(4); + var valuesFromEnumerable = source.ToList(); + List expectedValues = [(decimal)1/2]; + Assert.Equal(expectedValues, valuesFromEnumerable); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SkipErrorWhenSourceErrors_TrimFriendlySelectFalse() + { + RemoteExecutor.Invoke(() => + { + AppContext.SetSwitch("System.Linq.Enumerable.ValueTypeTrimFriendlySelect", false); + + var source = NumberRangeGuaranteedNotCollectionType(-2, 5).Select(i => (decimal)i).Select(m => 1 / m).Skip(4); + using (var en = source.GetEnumerator()) + { + Assert.Throws(() => en.MoveNext()); + } + }).Dispose(); } [Fact] diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index 1e4692999a174..ac0ef5e696463 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -1,5 +1,6 @@ + true $(NetCoreAppCurrent) true