From 017e47757893d9168c78ae374a22016650871bd5 Mon Sep 17 00:00:00 2001 From: JacobSilasBentley <30935918+JacobSilasBentley@users.noreply.github.com> Date: Wed, 1 May 2024 19:21:48 +0700 Subject: [PATCH 1/6] Use static iterator methods to avoid closure This is a squashed merge of PR #1064 that: - closes #906 - fixes #1065 --------- Co-authored-by: Atif Aziz --- MoreLinq.Test/RankTest.cs | 2 +- MoreLinq/Assert.cs | 4 +- MoreLinq/AssertCount.cs | 4 +- MoreLinq/Backsert.cs | 4 +- MoreLinq/Batch.cs | 4 +- MoreLinq/Cartesian.g.cs | 70 +++++++++++++++++++++++++--- MoreLinq/Cartesian.g.tt | 9 +++- MoreLinq/Choose.cs | 6 ++- MoreLinq/CountBy.cs | 4 +- MoreLinq/CountDown.cs | 21 ++++++--- MoreLinq/DistinctBy.cs | 7 ++- MoreLinq/EndsWith.cs | 8 ++-- MoreLinq/ExceptBy.cs | 8 +++- MoreLinq/Exclude.cs | 4 +- MoreLinq/Experimental/Async/Merge.cs | 4 +- MoreLinq/Experimental/Await.cs | 13 +++++- MoreLinq/Experimental/Batch.cs | 7 ++- MoreLinq/FallbackIfEmpty.cs | 11 ++++- MoreLinq/Flatten.cs | 14 ++++-- MoreLinq/From.cs | 12 +++-- MoreLinq/FullGroupJoin.cs | 10 +++- MoreLinq/FullJoin.cs | 12 ++++- MoreLinq/Generate.cs | 4 +- MoreLinq/Insert.cs | 4 +- MoreLinq/Lag.cs | 8 +++- MoreLinq/Lead.cs | 8 +++- MoreLinq/Maxima.cs | 7 ++- MoreLinq/Move.cs | 6 +-- MoreLinq/OrderedMerge.cs | 19 +++++++- MoreLinq/PadStart.cs | 8 +++- MoreLinq/Pairwise.cs | 4 +- MoreLinq/Permutations.cs | 4 +- MoreLinq/Pipe.cs | 4 +- MoreLinq/PreScan.cs | 7 ++- MoreLinq/Rank.cs | 4 +- MoreLinq/RunLengthEncode.cs | 4 +- MoreLinq/ScanBy.cs | 9 +++- MoreLinq/Segment.cs | 4 +- MoreLinq/SkipUntil.cs | 4 +- MoreLinq/Slice.cs | 6 +-- MoreLinq/SortedMerge.cs | 4 +- MoreLinq/Split.cs | 8 +++- MoreLinq/Subsets.cs | 8 +++- MoreLinq/TagFirstLast.cs | 4 +- MoreLinq/TakeUntil.cs | 4 +- MoreLinq/Transpose.cs | 4 +- MoreLinq/Traverse.cs | 8 +++- MoreLinq/Unfold.cs | 9 +++- MoreLinq/Window.cs | 4 +- MoreLinq/WindowLeft.cs | 4 +- MoreLinq/WindowRight.cs | 6 ++- 51 files changed, 326 insertions(+), 89 deletions(-) diff --git a/MoreLinq.Test/RankTest.cs b/MoreLinq.Test/RankTest.cs index ad36c7afb..6e84dc719 100644 --- a/MoreLinq.Test/RankTest.cs +++ b/MoreLinq.Test/RankTest.cs @@ -124,7 +124,7 @@ public void TestRankGroupedItems() .Concat(Enumerable.Range(0, count)) .Concat(Enumerable.Range(0, count)); using var ts = sequence.AsTestingSequence(); - var result = ts.Rank(); + var result = ts.Rank().ToArray(); Assert.That(result.Distinct().Count(), Is.EqualTo(count)); Assert.That(result, Is.EqualTo(sequence.Reverse().Select(x => x + 1))); diff --git a/MoreLinq/Assert.cs b/MoreLinq/Assert.cs index 1b272d8ed..0836113a6 100644 --- a/MoreLinq/Assert.cs +++ b/MoreLinq/Assert.cs @@ -65,7 +65,9 @@ public static IEnumerable Assert(this IEnumerable sou if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); - return _(); IEnumerable _() + return _(source, predicate, errorSelector); + + static IEnumerable _(IEnumerable source, Func predicate, Func? errorSelector) { foreach (var element in source) { diff --git a/MoreLinq/AssertCount.cs b/MoreLinq/AssertCount.cs index 14a55c1a4..f742f36c7 100644 --- a/MoreLinq/AssertCount.cs +++ b/MoreLinq/AssertCount.cs @@ -70,7 +70,9 @@ public static IEnumerable AssertCount(this IEnumerable _() + return _(source, count, errorSelector); + + static IEnumerable _(IEnumerable source, int count, Func errorSelector) { if (source.TryAsCollectionLike() is { Count: var collectionCount } && collectionCount.CompareTo(count) is var comparison && comparison != 0) diff --git a/MoreLinq/Backsert.cs b/MoreLinq/Backsert.cs index 490e7702e..47407bf83 100644 --- a/MoreLinq/Backsert.cs +++ b/MoreLinq/Backsert.cs @@ -65,10 +65,10 @@ public static IEnumerable Backsert(this IEnumerable first, IEnumerable< { < 0 => throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."), 0 => first.Concat(second), - _ => _() + _ => _(first, second, index) }; - IEnumerable _() + static IEnumerable _(IEnumerable first, IEnumerable second, int index) { using var e = first.CountDown(index, ValueTuple.Create).GetEnumerator(); diff --git a/MoreLinq/Batch.cs b/MoreLinq/Batch.cs index 5da60c07a..3d30e4f1f 100644 --- a/MoreLinq/Batch.cs +++ b/MoreLinq/Batch.cs @@ -86,7 +86,9 @@ public static IEnumerable Batch(this IEnumerable _() + return _(source, size, resultSelector); + + static IEnumerable _(IEnumerable source, int size, Func resultSelector) { switch (source) { diff --git a/MoreLinq/Cartesian.g.cs b/MoreLinq/Cartesian.g.cs index 64694c065..74740706c 100644 --- a/MoreLinq/Cartesian.g.cs +++ b/MoreLinq/Cartesian.g.cs @@ -69,7 +69,12 @@ public static IEnumerable Cartesian( if (second == null) throw new ArgumentNullException(nameof(second)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(first, second, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + Func resultSelector) { IEnumerable secondMemo; @@ -123,7 +128,13 @@ public static IEnumerable Cartesian( if (third == null) throw new ArgumentNullException(nameof(third)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(first, second, third, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; @@ -185,7 +196,14 @@ public static IEnumerable Cartesian( if (fourth == null) throw new ArgumentNullException(nameof(fourth)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(first, second, third, fourth, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + IEnumerable fourth, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; @@ -255,7 +273,15 @@ public static IEnumerable Cartesian( if (fifth == null) throw new ArgumentNullException(nameof(fifth)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(first, second, third, fourth, fifth, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + IEnumerable fourth, + IEnumerable fifth, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; @@ -333,7 +359,16 @@ public static IEnumerable Cartesian( if (sixth == null) throw new ArgumentNullException(nameof(sixth)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(first, second, third, fourth, fifth, sixth, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + IEnumerable fourth, + IEnumerable fifth, + IEnumerable sixth, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; @@ -419,7 +454,17 @@ public static IEnumerable Cartesian _() + return _(first, second, third, fourth, fifth, sixth, seventh, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + IEnumerable fourth, + IEnumerable fifth, + IEnumerable sixth, + IEnumerable seventh, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; @@ -513,7 +558,18 @@ public static IEnumerable Cartesian _() + return _(first, second, third, fourth, fifth, sixth, seventh, eighth, resultSelector); + + static IEnumerable _( + IEnumerable first, + IEnumerable second, + IEnumerable third, + IEnumerable fourth, + IEnumerable fifth, + IEnumerable sixth, + IEnumerable seventh, + IEnumerable eighth, + Func resultSelector) { IEnumerable secondMemo; IEnumerable thirdMemo; diff --git a/MoreLinq/Cartesian.g.tt b/MoreLinq/Cartesian.g.tt index 4bc112cea..4c078b9bc 100644 --- a/MoreLinq/Cartesian.g.tt +++ b/MoreLinq/Cartesian.g.tt @@ -115,7 +115,14 @@ Func<<#= string.Join(", ", from x in o.Arguments select "T" + x.Number) #>, TRes <# } #> if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(<#= string.Join(", ", from x in o.Arguments select x.Ordinal) #>, resultSelector); + + static IEnumerable _( + <# foreach (var arg in o.Arguments) { #> +IEnumerable> <#= arg.Ordinal #>, + <# + } #> +Func<<#= string.Join(", ", from x in o.Arguments select "T" + x.Number) #>, TResult> resultSelector) { <# foreach (var arg in o.Arguments.Skip(1)) { #> IEnumerable> <#= arg.Ordinal #>Memo; diff --git a/MoreLinq/Choose.cs b/MoreLinq/Choose.cs index bffbc72d9..865fb3928 100644 --- a/MoreLinq/Choose.cs +++ b/MoreLinq/Choose.cs @@ -55,7 +55,11 @@ public static IEnumerable Choose(this IEnumerable source if (source == null) throw new ArgumentNullException(nameof(source)); if (chooser == null) throw new ArgumentNullException(nameof(chooser)); - return _(); IEnumerable _() + return _(source, chooser); + + static IEnumerable _( + IEnumerable source, + Func chooser) { foreach (var item in source) { diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index c1f8873ed..0ea540d81 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -55,7 +55,9 @@ public static IEnumerable> CountBy(this I if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - return _(); IEnumerable> _() + return _(source, keySelector, comparer); + + static IEnumerable> _(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { List keys; List counts; diff --git a/MoreLinq/CountDown.cs b/MoreLinq/CountDown.cs index 53310e79a..aa0da23ce 100644 --- a/MoreLinq/CountDown.cs +++ b/MoreLinq/CountDown.cs @@ -58,12 +58,15 @@ public static IEnumerable CountDown(this IEnumerable sou if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); return source.TryAsListLike() is { } listLike - ? IterateList(listLike) + ? IterateList(listLike, count, resultSelector) : source.TryAsCollectionLike() is { } collectionLike - ? IterateCollection(collectionLike) - : IterateSequence(); + ? IterateCollection(collectionLike, count, resultSelector) + : IterateSequence(source, count, resultSelector); - IEnumerable IterateList(ListLike list) + static IEnumerable IterateList( + ListLike list, + int count, + Func resultSelector) { var listCount = list.Count; var countdown = Math.Min(count, listCount); @@ -72,14 +75,20 @@ IEnumerable IterateList(ListLike list) yield return resultSelector(list[i], listCount - i <= count ? --countdown : null); } - IEnumerable IterateCollection(CollectionLike collection) + static IEnumerable IterateCollection( + CollectionLike collection, + int count, + Func resultSelector) { var i = collection.Count; foreach (var item in collection) yield return resultSelector(item, i-- <= count ? i : null); } - IEnumerable IterateSequence() + static IEnumerable IterateSequence( + IEnumerable source, + int count, + Func resultSelector) { var queue = new Queue(Math.Max(1, count + 1)); diff --git a/MoreLinq/DistinctBy.cs b/MoreLinq/DistinctBy.cs index 1079ff814..834e60f70 100644 --- a/MoreLinq/DistinctBy.cs +++ b/MoreLinq/DistinctBy.cs @@ -78,7 +78,12 @@ public static IEnumerable DistinctBy(this IEnumerable _() + return _(source, keySelector, comparer); + + static IEnumerable _( + IEnumerable source, + Func keySelector, + IEqualityComparer? comparer) { var knownKeys = new HashSet(comparer); foreach (var element in source) diff --git a/MoreLinq/EndsWith.cs b/MoreLinq/EndsWith.cs index d414a3dc0..99ce9428f 100644 --- a/MoreLinq/EndsWith.cs +++ b/MoreLinq/EndsWith.cs @@ -78,14 +78,14 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second, return second.TryAsCollectionLike() is { Count: var secondCount } ? first.TryAsCollectionLike() is { Count: var firstCount } && secondCount > firstCount ? false - : Impl(second, secondCount) - : Impl(secondList = second.ToList(), secondList.Count); + : Impl(first, second, secondCount, comparer) + : Impl(first, secondList = second.ToList(), secondList.Count, comparer); #pragma warning restore IDE0075 // Simplify conditional expression - bool Impl(IEnumerable snd, int count) + static bool Impl(IEnumerable first, IEnumerable second, int count, IEqualityComparer comparer) { using var firstIter = first.TakeLast(count).GetEnumerator(); - return snd.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); + return second.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); } } } diff --git a/MoreLinq/ExceptBy.cs b/MoreLinq/ExceptBy.cs index 6a5e1f2c6..2a8fe7911 100644 --- a/MoreLinq/ExceptBy.cs +++ b/MoreLinq/ExceptBy.cs @@ -79,9 +79,13 @@ public static IEnumerable ExceptBy(this IEnumerable Impl() + static IEnumerable Impl( + IEnumerable first, + IEnumerable second, + Func keySelector, + IEqualityComparer? keyComparer) { // TODO Use ToHashSet var keys = new HashSet(second.Select(keySelector), keyComparer); diff --git a/MoreLinq/Exclude.cs b/MoreLinq/Exclude.cs index 455861862..6d416d0fa 100644 --- a/MoreLinq/Exclude.cs +++ b/MoreLinq/Exclude.cs @@ -41,10 +41,10 @@ public static IEnumerable Exclude(this IEnumerable sequence, int startI { < 0 => throw new ArgumentOutOfRangeException(nameof(count)), 0 => sequence, - _ => _() + _ => _(sequence, startIndex, count) }; - IEnumerable _() + static IEnumerable _(IEnumerable sequence, int startIndex, int count) { var index = 0; var endIndex = startIndex + count; diff --git a/MoreLinq/Experimental/Async/Merge.cs b/MoreLinq/Experimental/Async/Merge.cs index f414eaa7d..03ae97c82 100644 --- a/MoreLinq/Experimental/Async/Merge.cs +++ b/MoreLinq/Experimental/Async/Merge.cs @@ -85,9 +85,9 @@ public static IAsyncEnumerable Merge(this IEnumerable> if (sources is null) throw new ArgumentNullException(nameof(sources)); if (maxConcurrent <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrent)); - return Async(); + return Async(sources, maxConcurrent); - async IAsyncEnumerable Async([EnumeratorCancellation]CancellationToken cancellationToken = default) + static async IAsyncEnumerable Async(IEnumerable> sources, int maxConcurrent, [EnumeratorCancellation]CancellationToken cancellationToken = default) { using var thisCancellationTokenSource = new CancellationTokenSource(); diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 717a209bc..dca801b51 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -424,11 +424,20 @@ public static IAwaitQuery AwaitCompletion( return AwaitQuery.Create( - options => Impl(options.MaxConcurrency, + options => Impl(source, + evaluator, + resultSelector, + options.MaxConcurrency, options.Scheduler ?? TaskScheduler.Default, options.PreserveOrder)); - IEnumerable Impl(int? maxConcurrency, TaskScheduler scheduler, bool ordered) + static IEnumerable Impl( + IEnumerable source, + Func> evaluator, + Func, TResult> resultSelector, + int? maxConcurrency, + TaskScheduler scheduler, + bool ordered) { // A separate task will enumerate the source and launch tasks. // It will post all progress as notices to the collection below. diff --git a/MoreLinq/Experimental/Batch.cs b/MoreLinq/Experimental/Batch.cs index 9e9c28425..d3c444804 100644 --- a/MoreLinq/Experimental/Batch.cs +++ b/MoreLinq/Experimental/Batch.cs @@ -145,7 +145,12 @@ public static IEnumerable if (bucketProjectionSelector == null) throw new ArgumentNullException(nameof(bucketProjectionSelector)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(source, size, pool, bucketProjectionSelector, resultSelector); + + static IEnumerable _( + IEnumerable source, int size, ArrayPool pool, + Func, IEnumerable> bucketProjectionSelector, + Func, TResult> resultSelector) { using var batch = source.Batch(size, pool); var bucket = bucketProjectionSelector(batch.CurrentBuffer); diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index 159a7de4f..a54db158c 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -161,7 +161,16 @@ static IEnumerable FallbackIfEmptyImpl(IEnumerable source, int? count, T? fallback1, T? fallback2, T? fallback3, T? fallback4, IEnumerable? fallback) { - return _(); IEnumerable _() + return _(source, count, fallback1, fallback2, fallback3, fallback4, fallback); + + static IEnumerable _( + IEnumerable source, + int? count, + T? fallback1, + T? fallback2, + T? fallback3, + T? fallback4, + IEnumerable? fallback) { if (source.TryAsCollectionLike() is null or { Count: > 0 }) { diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index eaee331d2..b2f5eb739 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -118,14 +118,20 @@ public static IEnumerable< if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); - return _(); + return _(source, selector); - IEnumerable< + static IEnumerable< // Just like "IEnumerable.Current" is null-oblivious, so is this: #nullable disable -/*...................*/ object +/*..........................*/ object #nullable restore -/*.........................*/ > _() +/*.................................*/ > _(IEnumerable source, + Func< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*..........................................*/ object, +#nullable restore +/*..................................................*/ IEnumerable?> selector) { var e = source.GetEnumerator(); var stack = new Stack(); diff --git a/MoreLinq/From.cs b/MoreLinq/From.cs index b9bf76faa..bf2e826d6 100644 --- a/MoreLinq/From.cs +++ b/MoreLinq/From.cs @@ -36,7 +36,9 @@ partial class MoreEnumerable public static IEnumerable From(Func function) { - return _(); IEnumerable _() + return _(function); + + static IEnumerable _(Func function) { #pragma warning disable CA1062 // Validate arguments of public methods yield return function(); @@ -59,7 +61,9 @@ public static IEnumerable From(Func function) public static IEnumerable From(Func function1, Func function2) { - return _(); IEnumerable _() + return _(function1, function2); + + static IEnumerable _(Func function1, Func function2) { #pragma warning disable CA1062 // Validate arguments of public methods yield return function1(); @@ -84,7 +88,9 @@ public static IEnumerable From(Func function1, Func function2) public static IEnumerable From(Func function1, Func function2, Func function3) { - return _(); IEnumerable _() + return _(function1, function2, function3); + + static IEnumerable _(Func function1, Func function2, Func function3) { #pragma warning disable CA1062 // Validate arguments of public methods yield return function1(); diff --git a/MoreLinq/FullGroupJoin.cs b/MoreLinq/FullGroupJoin.cs index 2f6fb56c8..455940cc0 100644 --- a/MoreLinq/FullGroupJoin.cs +++ b/MoreLinq/FullGroupJoin.cs @@ -146,9 +146,15 @@ public static IEnumerable FullGroupJoin if (secondKeySelector == null) throw new ArgumentNullException(nameof(secondKeySelector)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(comparer ?? EqualityComparer.Default); + return _(first, second, firstKeySelector, secondKeySelector, resultSelector, comparer ?? EqualityComparer.Default); - IEnumerable _(IEqualityComparer comparer) + static IEnumerable _( + IEnumerable first, + IEnumerable second, + Func firstKeySelector, + Func secondKeySelector, + Func, IEnumerable, TResult> resultSelector, + IEqualityComparer comparer) { var alookup = Lookup.CreateForJoin(first, firstKeySelector, comparer); var blookup = Lookup.CreateForJoin(second, secondKeySelector, comparer); diff --git a/MoreLinq/FullJoin.cs b/MoreLinq/FullJoin.cs index 9b2da6d12..6d59386e7 100644 --- a/MoreLinq/FullJoin.cs +++ b/MoreLinq/FullJoin.cs @@ -228,9 +228,17 @@ public static IEnumerable FullJoin( if (secondSelector == null) throw new ArgumentNullException(nameof(secondSelector)); if (bothSelector == null) throw new ArgumentNullException(nameof(bothSelector)); - return Impl(); + return Impl(first, second, firstKeySelector, secondKeySelector, firstSelector, secondSelector, bothSelector, comparer); - IEnumerable Impl() + static IEnumerable Impl( + IEnumerable first, + IEnumerable second, + Func firstKeySelector, + Func secondKeySelector, + Func firstSelector, + Func secondSelector, + Func bothSelector, + IEqualityComparer? comparer) { var seconds = second.Select(e => new KeyValuePair(secondKeySelector(e), e)).ToArray(); var secondLookup = seconds.ToLookup(e => e.Key, e => e.Value, comparer); diff --git a/MoreLinq/Generate.cs b/MoreLinq/Generate.cs index a15b38486..519e9785f 100644 --- a/MoreLinq/Generate.cs +++ b/MoreLinq/Generate.cs @@ -45,7 +45,9 @@ public static IEnumerable Generate(TResult initial, Func _() + return _(initial, generator); + + static IEnumerable _(TResult initial, Func generator) { var current = initial; while (true) diff --git a/MoreLinq/Insert.cs b/MoreLinq/Insert.cs index 11c337ba3..774a7df9f 100644 --- a/MoreLinq/Insert.cs +++ b/MoreLinq/Insert.cs @@ -55,7 +55,9 @@ public static IEnumerable Insert(this IEnumerable first, IEnumerable if (second == null) throw new ArgumentNullException(nameof(second)); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."); - return _(); IEnumerable _() + return _(first, second, index); + + static IEnumerable _(IEnumerable first, IEnumerable second, int index) { var i = -1; diff --git a/MoreLinq/Lag.cs b/MoreLinq/Lag.cs index 4b532a649..f3ac4cacb 100644 --- a/MoreLinq/Lag.cs +++ b/MoreLinq/Lag.cs @@ -83,7 +83,13 @@ public static IEnumerable Lag(this IEnumerable _() + return _(source, offset, defaultLagValue, resultSelector); + + static IEnumerable _( + IEnumerable source, + int offset, + TSource defaultLagValue, + Func resultSelector) { using var iter = source.GetEnumerator(); diff --git a/MoreLinq/Lead.cs b/MoreLinq/Lead.cs index 2a0b7cbb3..392e55b99 100644 --- a/MoreLinq/Lead.cs +++ b/MoreLinq/Lead.cs @@ -80,7 +80,13 @@ public static IEnumerable Lead(this IEnumerable _() + return _(source, offset, defaultLeadValue, resultSelector); + + static IEnumerable _( + IEnumerable source, + int offset, + TSource defaultLeadValue, + Func resultSelector) { var leadQueue = new Queue(offset); using var iter = source.GetEnumerator(); diff --git a/MoreLinq/Maxima.cs b/MoreLinq/Maxima.cs index 645ea374d..9f4a35c1a 100644 --- a/MoreLinq/Maxima.cs +++ b/MoreLinq/Maxima.cs @@ -312,10 +312,13 @@ static IEnumerable ExtremaBy( Extrema extrema, int? limit, Func selector, Func comparer) { - foreach (var item in Extrema()) + foreach (var item in Extrema(source, extrema, limit, selector, comparer)) yield return item; - IEnumerable Extrema() + static IEnumerable Extrema( + IEnumerable source, + Extrema extrema, int? limit, + Func selector, Func comparer) { using var e = source.GetEnumerator(); diff --git a/MoreLinq/Move.cs b/MoreLinq/Move.cs index 43e38d2d7..73dbfc849 100644 --- a/MoreLinq/Move.cs +++ b/MoreLinq/Move.cs @@ -58,10 +58,10 @@ public static IEnumerable Move(this IEnumerable source, int fromIndex, return source; return toIndex < fromIndex - ? _(toIndex, fromIndex - toIndex, count) - : _(fromIndex, count, toIndex - fromIndex); + ? _(source, toIndex, fromIndex - toIndex, count) + : _(source, fromIndex, count, toIndex - fromIndex); - IEnumerable _(int bufferStartIndex, int bufferSize, int bufferYieldIndex) + static IEnumerable _(IEnumerable source, int bufferStartIndex, int bufferSize, int bufferYieldIndex) { var hasMore = true; bool MoveNext(IEnumerator e) => hasMore && (hasMore = e.MoveNext()); diff --git a/MoreLinq/OrderedMerge.cs b/MoreLinq/OrderedMerge.cs index 0e30e1517..3f4a7e5e8 100644 --- a/MoreLinq/OrderedMerge.cs +++ b/MoreLinq/OrderedMerge.cs @@ -282,9 +282,24 @@ public static IEnumerable OrderedMerge( if (bothSelector == null) throw new ArgumentNullException(nameof(bothSelector)); if (secondSelector == null) throw new ArgumentNullException(nameof(secondSelector)); - return _(comparer ?? Comparer.Default); + return _(first, + second, + firstKeySelector, + secondKeySelector, + firstSelector, + secondSelector, + bothSelector, + comparer ?? Comparer.Default); - IEnumerable _(IComparer comparer) + static IEnumerable _( + IEnumerable first, + IEnumerable second, + Func firstKeySelector, + Func secondKeySelector, + Func firstSelector, + Func secondSelector, + Func bothSelector, + IComparer comparer) { using var e1 = first.GetEnumerator(); using var e2 = second.GetEnumerator(); diff --git a/MoreLinq/PadStart.cs b/MoreLinq/PadStart.cs index 5e9e380d1..639e15305 100644 --- a/MoreLinq/PadStart.cs +++ b/MoreLinq/PadStart.cs @@ -117,7 +117,13 @@ public static IEnumerable PadStart(this IEnumerable s static IEnumerable PadStartImpl(IEnumerable source, int width, T? padding, Func? paddingSelector) { - return _(); IEnumerable _() + return _(source, width, padding, paddingSelector); + + static IEnumerable _( + IEnumerable source, + int width, + T? padding, + Func? paddingSelector) { if (source.TryAsCollectionLike() is { Count: var collectionCount } && collectionCount < width) { diff --git a/MoreLinq/Pairwise.cs b/MoreLinq/Pairwise.cs index 9e262e85d..6b9c4347d 100644 --- a/MoreLinq/Pairwise.cs +++ b/MoreLinq/Pairwise.cs @@ -53,7 +53,9 @@ public static IEnumerable Pairwise(this IEnumerable _() + return _(source, resultSelector); + + static IEnumerable _(IEnumerable source, Func resultSelector) { using var e = source.GetEnumerator(); diff --git a/MoreLinq/Permutations.cs b/MoreLinq/Permutations.cs index 2324750bb..dd60235a4 100644 --- a/MoreLinq/Permutations.cs +++ b/MoreLinq/Permutations.cs @@ -49,7 +49,9 @@ public static IEnumerable> Permutations(this IEnumerable sequence { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); - return _(); IEnumerable> _() + return _(sequence); + + static IEnumerable> _(IEnumerable sequence) { // The algorithm used to generate permutations uses the fact that any set can be put // into 1-to-1 correspondence with the set of ordinals number (0..n). The diff --git a/MoreLinq/Pipe.cs b/MoreLinq/Pipe.cs index 474ef725e..94c5c6138 100644 --- a/MoreLinq/Pipe.cs +++ b/MoreLinq/Pipe.cs @@ -43,7 +43,9 @@ public static IEnumerable Pipe(this IEnumerable source, Action actio if (source == null) throw new ArgumentNullException(nameof(source)); if (action == null) throw new ArgumentNullException(nameof(action)); - return _(); IEnumerable _() + return _(source, action); + + static IEnumerable _(IEnumerable source, Action action) { foreach (var element in source) { diff --git a/MoreLinq/PreScan.cs b/MoreLinq/PreScan.cs index 88aa8f57c..a23763722 100644 --- a/MoreLinq/PreScan.cs +++ b/MoreLinq/PreScan.cs @@ -59,7 +59,12 @@ public static IEnumerable PreScan( if (source == null) throw new ArgumentNullException(nameof(source)); if (transformation == null) throw new ArgumentNullException(nameof(transformation)); - return _(); IEnumerable _() + return _(source, transformation, identity); + + static IEnumerable _( + IEnumerable source, + Func transformation, + TSource identity) { var aggregator = identity; using var e = source.GetEnumerator(); diff --git a/MoreLinq/Rank.cs b/MoreLinq/Rank.cs index 4e5586382..4232fac00 100644 --- a/MoreLinq/Rank.cs +++ b/MoreLinq/Rank.cs @@ -77,9 +77,9 @@ public static IEnumerable RankBy(this IEnumerable s if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - return _(comparer ?? Comparer.Default); + return _(source, keySelector, comparer ?? Comparer.Default); - IEnumerable _(IComparer comparer) + static IEnumerable _(IEnumerable source, Func keySelector, IComparer comparer) { source = source.ToArray(); // avoid enumerating source twice diff --git a/MoreLinq/RunLengthEncode.cs b/MoreLinq/RunLengthEncode.cs index 232c38e37..f701de31f 100644 --- a/MoreLinq/RunLengthEncode.cs +++ b/MoreLinq/RunLengthEncode.cs @@ -49,9 +49,9 @@ public static IEnumerable> RunLengthEncode(this IEnumera { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); - return _(comparer ?? EqualityComparer.Default); + return _(sequence, comparer ?? EqualityComparer.Default); - IEnumerable> _(IEqualityComparer comparer) + static IEnumerable> _(IEnumerable sequence, IEqualityComparer comparer) { // This implementation could also have been written using a foreach loop, // but it proved to be easier to deal with edge certain cases that occur diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index 3ca138ced..d12d1de7f 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -85,9 +85,14 @@ public static IEnumerable> ScanBy.Default); + return _(source, keySelector, seedSelector, accumulator, comparer ?? EqualityComparer.Default); - IEnumerable> _(IEqualityComparer comparer) + static IEnumerable> _( + IEnumerable source, + Func keySelector, + Func seedSelector, + Func accumulator, + IEqualityComparer comparer) { var stateByKey = new Collections.Dictionary(comparer); diff --git a/MoreLinq/Segment.cs b/MoreLinq/Segment.cs index 859f59014..e29ecdce0 100644 --- a/MoreLinq/Segment.cs +++ b/MoreLinq/Segment.cs @@ -74,7 +74,9 @@ public static IEnumerable> Segment(this IEnumerable source, if (source == null) throw new ArgumentNullException(nameof(source)); if (newSegmentPredicate == null) throw new ArgumentNullException(nameof(newSegmentPredicate)); - return _(); IEnumerable> _() + return _(source, newSegmentPredicate); + + static IEnumerable> _(IEnumerable source, Func newSegmentPredicate) { using var e = source.GetEnumerator(); diff --git a/MoreLinq/SkipUntil.cs b/MoreLinq/SkipUntil.cs index 7a738c35c..07a45f667 100644 --- a/MoreLinq/SkipUntil.cs +++ b/MoreLinq/SkipUntil.cs @@ -57,7 +57,9 @@ public static IEnumerable SkipUntil(this IEnumerable if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); - return _(); IEnumerable _() + return _(source, predicate); + + static IEnumerable _(IEnumerable source, Func predicate) { using var enumerator = source.GetEnumerator(); diff --git a/MoreLinq/Slice.cs b/MoreLinq/Slice.cs index 86f8306fa..5867d58fa 100644 --- a/MoreLinq/Slice.cs +++ b/MoreLinq/Slice.cs @@ -55,12 +55,12 @@ public static IEnumerable Slice(this IEnumerable sequence, int startInd return sequence switch { - IList list => SliceList(list.Count, i => list[i]), - IReadOnlyList list => SliceList(list.Count, i => list[i]), + IList list => SliceList(startIndex, count, list.Count, i => list[i]), + IReadOnlyList list => SliceList(startIndex, count, list.Count, i => list[i]), var seq => seq.Skip(startIndex).Take(count) }; - IEnumerable SliceList(int listCount, Func indexer) + static IEnumerable SliceList(int startIndex, int count, int listCount, Func indexer) { var countdown = count; var index = startIndex; diff --git a/MoreLinq/SortedMerge.cs b/MoreLinq/SortedMerge.cs index 03757dcb5..04d328099 100644 --- a/MoreLinq/SortedMerge.cs +++ b/MoreLinq/SortedMerge.cs @@ -96,7 +96,7 @@ public static IEnumerable SortedMerge(this IEnumerable comparer.Compare(b, a) > 0; // return the sorted merge result - return Impl(new[] { source }.Concat(otherSequences)); + return Impl(new[] { source }.Concat(otherSequences), precedenceFunc); // Private implementation method that performs a merge of multiple, ordered sequences using // a precedence function which encodes order-sensitive comparison logic based on the caller's arguments. @@ -109,7 +109,7 @@ public static IEnumerable SortedMerge(this IEnumerableN => otherSequences.Count()+1. - IEnumerable Impl(IEnumerable> sequences) + static IEnumerable Impl(IEnumerable> sequences, Func precedenceFunc) { using var disposables = new DisposableGroup(sequences.Select(e => e.GetEnumerator()).Acquire()); diff --git a/MoreLinq/Split.cs b/MoreLinq/Split.cs index 935537aa5..5edb1f0ba 100644 --- a/MoreLinq/Split.cs +++ b/MoreLinq/Split.cs @@ -267,7 +267,13 @@ public static IEnumerable Split(this IEnumerable _() + return _(source, separatorFunc, count, resultSelector); + + static IEnumerable _( + IEnumerable source, + Func separatorFunc, + int count, + Func, TResult> resultSelector) { if (count == 0) // No splits? { diff --git a/MoreLinq/Subsets.cs b/MoreLinq/Subsets.cs index f1167152f..9ffe27f05 100644 --- a/MoreLinq/Subsets.cs +++ b/MoreLinq/Subsets.cs @@ -51,7 +51,9 @@ public static IEnumerable> Subsets(this IEnumerable sequence) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); - return _(); IEnumerable> _() + return _(sequence); + + static IEnumerable> _(IEnumerable sequence) { var sequenceAsList = sequence.ToList(); var sequenceLength = sequenceAsList.Count; @@ -113,7 +115,9 @@ public static IEnumerable> Subsets(this IEnumerable sequence, int // preconditions. This however, needs to be carefully considered - and perhaps // may change after further thought and review. - return _(); IEnumerable> _() + return _(sequence, subsetSize); + + static IEnumerable> _(IEnumerable sequence, int subsetSize) { foreach (var subset in Subsets(sequence.ToList(), subsetSize)) yield return subset; diff --git a/MoreLinq/TagFirstLast.cs b/MoreLinq/TagFirstLast.cs index 61d90f6e7..705b22a04 100644 --- a/MoreLinq/TagFirstLast.cs +++ b/MoreLinq/TagFirstLast.cs @@ -59,7 +59,9 @@ public static IEnumerable TagFirstLast(this IEnumerab if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(); IEnumerable _() + return _(source, resultSelector); + + static IEnumerable _(IEnumerable source, Func resultSelector) { using var enumerator = source.GetEnumerator(); diff --git a/MoreLinq/TakeUntil.cs b/MoreLinq/TakeUntil.cs index bceb05c22..3488a3bec 100644 --- a/MoreLinq/TakeUntil.cs +++ b/MoreLinq/TakeUntil.cs @@ -57,7 +57,9 @@ public static IEnumerable TakeUntil(this IEnumerable if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); - return _(); IEnumerable _() + return _(source, predicate); + + static IEnumerable _(IEnumerable source, Func predicate) { foreach (var item in source) { diff --git a/MoreLinq/Transpose.cs b/MoreLinq/Transpose.cs index eb1a151bd..e6841a9ca 100644 --- a/MoreLinq/Transpose.cs +++ b/MoreLinq/Transpose.cs @@ -56,7 +56,9 @@ public static IEnumerable> Transpose(this IEnumerable> _() + return _(source); + + static IEnumerable> _(IEnumerable> source) { #pragma warning disable IDE0007 // Use implicit type (false positive) IEnumerator?[] enumerators = source.Select(e => e.GetEnumerator()).Acquire(); diff --git a/MoreLinq/Traverse.cs b/MoreLinq/Traverse.cs index 4fb46b5b8..1ec5b0d9b 100644 --- a/MoreLinq/Traverse.cs +++ b/MoreLinq/Traverse.cs @@ -48,7 +48,9 @@ public static IEnumerable TraverseBreadthFirst(T root, Func _() + return _(root, childrenSelector); + + static IEnumerable _(T root, Func> childrenSelector) { var queue = new Queue(); queue.Enqueue(root); @@ -88,7 +90,9 @@ public static IEnumerable TraverseDepthFirst(T root, Func _() + return _(root, childrenSelector); + + static IEnumerable _(T root, Func> childrenSelector) { var stack = new Stack(); stack.Push(root); diff --git a/MoreLinq/Unfold.cs b/MoreLinq/Unfold.cs index d6f5b8738..380a10858 100644 --- a/MoreLinq/Unfold.cs +++ b/MoreLinq/Unfold.cs @@ -61,7 +61,14 @@ public static IEnumerable Unfold( if (stateSelector == null) throw new ArgumentNullException(nameof(stateSelector)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return _(state); IEnumerable _(TState initialState) + return _(state, generator, predicate, stateSelector, resultSelector); + + static IEnumerable _( + TState initialState, + Func generator, + Func predicate, + Func stateSelector, + Func resultSelector) { for (var state = initialState; ;) { diff --git a/MoreLinq/Window.cs b/MoreLinq/Window.cs index 04b1ddefd..e3de531f7 100644 --- a/MoreLinq/Window.cs +++ b/MoreLinq/Window.cs @@ -44,7 +44,9 @@ public static IEnumerable> Window(this IEnumerable> _() + return _(source, size); + + static IEnumerable> _(IEnumerable source, int size) { using var iter = source.GetEnumerator(); diff --git a/MoreLinq/WindowLeft.cs b/MoreLinq/WindowLeft.cs index fdcd72803..43dc0ad8a 100644 --- a/MoreLinq/WindowLeft.cs +++ b/MoreLinq/WindowLeft.cs @@ -63,7 +63,9 @@ public static IEnumerable> WindowLeft(this IEnumerable> _() + return _(source, size); + + static IEnumerable> _(IEnumerable source, int size) { var window = new List(); foreach (var item in source) diff --git a/MoreLinq/WindowRight.cs b/MoreLinq/WindowRight.cs index 4172c3d6d..52bd9a050 100644 --- a/MoreLinq/WindowRight.cs +++ b/MoreLinq/WindowRight.cs @@ -78,7 +78,11 @@ static IEnumerable> WindowRightWhile( if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); - return _(); IEnumerable> _() + return _(source, predicate); + + static IEnumerable> _( + IEnumerable source, + Func predicate) { var window = new List(); foreach (var item in source) From bd10cf9d10e400de2f634917787c047f9d687974 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 6 May 2024 22:39:58 +0500 Subject: [PATCH 2/6] Update NuGet package validation tool to latest version --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a3de0d7a5..890982194 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "meziantou.framework.nugetpackagevalidation.tool": { - "version": "1.0.12", + "version": "1.0.14", "commands": [ "meziantou.validate-nuget-package" ] From 9a2cc9f3a0fe94ea11a0be1371c97c2e7ac01568 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 6 May 2024 22:10:15 +0200 Subject: [PATCH 3/6] Remove testing against .NET 7 (near end-of-life) .NET 7 reaches end-of-life on May 14, 2024: https://dotnet.microsoft.com/en-us/download/dotnet/7.0 --- .github/workflows/build.yml | 5 ----- MoreLinq.Test/MoreLinq.Test.csproj | 2 +- appveyor.yml | 2 -- test.cmd | 2 -- test.sh | 2 +- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f51ded59d..d7edc5aab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,11 +58,6 @@ jobs: with: global-json-file: global.json - - name: Setup .NET SDK 7.0 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 7.0.x - - name: Setup .NET SDK 6.0 uses: actions/setup-dotnet@v3 with: diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index 413e5e7f4..5defc11f4 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -2,7 +2,7 @@ MoreLinq.Test - net8.0;net7.0;net6.0;net471 + net8.0;net6.0;net471 portable MoreLinq.Test Exe diff --git a/appveyor.yml b/appveyor.yml index c7f50f424..646a17b24 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,10 +52,8 @@ install: - git reset --hard - ps: if ($isWindows) { tools\dotnet-install.ps1 -JSonFile global.json } - ps: if ($isWindows) { tools\dotnet-install.ps1 -Runtime dotnet -Version 6.0.11 -SkipNonVersionedFiles } -- ps: if ($isWindows) { tools\dotnet-install.ps1 -Runtime dotnet -Version 7.0.14 -SkipNonVersionedFiles } - sh: ./tools/dotnet-install.sh --jsonfile global.json - sh: ./tools/dotnet-install.sh --runtime dotnet --version 6.0.11 --skip-non-versioned-files -- sh: ./tools/dotnet-install.sh --runtime dotnet --version 7.0.14 --skip-non-versioned-files - sh: export PATH="$HOME/.dotnet:$PATH" before_build: - dotnet --info diff --git a/test.cmd b/test.cmd index 640f802b9..a6d28730d 100644 --- a/test.cmd +++ b/test.cmd @@ -10,8 +10,6 @@ if %SKIP_TEST_BUILD%==false call build || exit /b 1 call :clean ^ && call :test net8.0 Debug ^ && call :test net8.0 Release ^ - && call :test net7.0 Debug ^ - && call :test net7.0 Release ^ && call :test net6.0 Debug ^ && call :test net6.0 Release ^ && call :test net471 Debug ^ diff --git a/test.sh b/test.sh index fbe65fd50..9bd9806a0 100755 --- a/test.sh +++ b/test.sh @@ -12,7 +12,7 @@ if [[ -z "$1" ]]; then else configs="$1" fi -for f in net6.0 net7.0 net8.0; do +for f in net6.0 net8.0; do for c in $configs; do dotnet test --no-build -c $c -f $f --settings MoreLinq.Test/coverlet.runsettings MoreLinq.Test TEST_RESULTS_DIR="$(ls -dc MoreLinq.Test/TestResults/* | head -1)" From bb478ee905fbd516dd4c1e4b3e7911767e863232 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 20 May 2024 14:11:00 +0200 Subject: [PATCH 4/6] Pin to .NET SDK 8.0.2xx --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index 391ba3c2a..d031a7632 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "latestFeature" + "version": "8.0.200", + "rollForward": "latestPatch" } } From 2bedad6e79e05e0ece5ce5c605ca243ed1061902 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 6 Jul 2024 15:25:24 +0200 Subject: [PATCH 5/6] Enable Native AOT compatibility (trimming support) This is a squashed merge of PR #1070. --- MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj | 32 +++ MoreLinq.Test.Aot/ToDataTableTest.cs | 235 +++++++++++++++++++++ MoreLinq.Test/Throws.cs | 9 +- MoreLinq.Test/ToDataTableTest.cs | 32 +-- MoreLinq.sln | 6 + MoreLinq/Extensions.ToDataTable.g.cs | 18 +- MoreLinq/MoreLinq.csproj | 9 + MoreLinq/ToDataTable.cs | 148 ++++++++++--- test.cmd | 21 +- test.sh | 2 + 10 files changed, 456 insertions(+), 56 deletions(-) create mode 100644 MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj create mode 100644 MoreLinq.Test.Aot/ToDataTableTest.cs diff --git a/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj b/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj new file mode 100644 index 000000000..9453f8dc7 --- /dev/null +++ b/MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + exe + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/MoreLinq.Test.Aot/ToDataTableTest.cs b/MoreLinq.Test.Aot/ToDataTableTest.cs new file mode 100644 index 000000000..163c76598 --- /dev/null +++ b/MoreLinq.Test.Aot/ToDataTableTest.cs @@ -0,0 +1,235 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2024 Atif Aziz. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq.Test +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Data; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + + [TestClass] + public class ToDataTableTest + { + sealed class TestObject(int key) + { + public int KeyField = key; + public Guid? ANullableGuidField = Guid.NewGuid(); + + public string AString { get; } = "ABCDEFGHIKKLMNOPQRSTUVWXYSZ"; + public decimal? ANullableDecimal { get; } = key / 3; + public object Unreadable { set => throw new NotImplementedException(); } + + public object this[int index] { get => new(); set { } } + + public override string ToString() => nameof(TestObject); + } + + readonly IReadOnlyCollection testObjects; + + public ToDataTableTest() => + this.testObjects = Enumerable.Range(0, 3) + .Select(i => new TestObject(i)) + .ToArray(); + + [TestMethod] + public void ToDataTableNullMemberExpressionMethod() + { + Expression>? expression = null; + + [UnconditionalSuppressMessage("Aot", "IL2026")] + void Act() => _ = this.testObjects.ToDataTable(expression!); + + var e = Assert.ThrowsException(Act); + Assert.AreEqual("expressions", e.ParamName); + } + + [TestMethod] + public void ToDataTableTableWithWrongColumnNames() + { + using var dt = new DataTable(); + _ = dt.Columns.Add("Test"); + + [UnconditionalSuppressMessage("Aot", "IL2026")] + void Act() => _ = this.testObjects.ToDataTable(dt); + + var e = Assert.ThrowsException(Act); + Assert.AreEqual("table", e.ParamName); + } + + [TestMethod] + public void ToDataTableTableWithWrongColumnDataType() + { + using var dt = new DataTable(); + _ = dt.Columns.Add("AString", typeof(int)); + + [UnconditionalSuppressMessage("Aot", "IL2026")] + void Act() => _ = this.testObjects.ToDataTable(dt, t => t.AString); + + var e = Assert.ThrowsException(Act); + Assert.AreEqual("table", e.ParamName); + } + + void TestDataTableMemberExpression(Expression> expression) + { + [UnconditionalSuppressMessage("Aot", "IL2026")] + void Act() => _ = this.testObjects.ToDataTable(expression); + + var e = Assert.ThrowsException(Act); + Assert.AreEqual("expressions", e.ParamName); + var innerException = e.InnerException; + Assert.IsNotNull(innerException); + Assert.IsInstanceOfType(innerException); + Assert.AreEqual("lambda", ((ArgumentException)innerException).ParamName); + } + + [TestMethod] + public void ToDataTableMemberExpressionMethod() + { + TestDataTableMemberExpression(t => t.ToString()); + } + + [TestMethod] + public void ToDataTableMemberExpressionNonMember() + { + TestDataTableMemberExpression(t => t.ToString().Length); + } + + [TestMethod] + public void ToDataTableMemberExpressionIndexer() + { + TestDataTableMemberExpression(t => t[0]); + } + + [TestMethod] + public void ToDataTableMemberExpressionStatic() + { + TestDataTableMemberExpression(_ => DateTime.Now); + } + + [TestMethod] + public void ToDataTableSchemaInDeclarationOrder() + { + [UnconditionalSuppressMessage("Aot", "IL2026")] + DataTable Act() => this.testObjects.ToDataTable(); + + var dt = Act(); + + // Assert properties first, then fields, then in declaration order + + Assert.AreEqual("KeyField", dt.Columns[2].Caption); + Assert.AreEqual(typeof(int), dt.Columns[2].DataType); + + Assert.AreEqual("ANullableGuidField", dt.Columns[3].Caption); + Assert.AreEqual(typeof(Guid), dt.Columns[3].DataType); + Assert.IsTrue(dt.Columns[3].AllowDBNull); + + Assert.AreEqual("AString", dt.Columns[0].Caption); + Assert.AreEqual(typeof(string), dt.Columns[0].DataType); + + Assert.AreEqual("ANullableDecimal", dt.Columns[1].Caption); + Assert.AreEqual(typeof(decimal), dt.Columns[1].DataType); + + Assert.AreEqual(4, dt.Columns.Count); + } + + [TestMethod] + public void ToDataTableContainsAllElements() + { + [UnconditionalSuppressMessage("Aot", "IL2026")] + DataTable Act() => this.testObjects.ToDataTable(); + + var dt = Act(); + + Assert.AreEqual(this.testObjects.Count, dt.Rows.Count); + } + + [TestMethod] + public void ToDataTableWithExpression() + { + [UnconditionalSuppressMessage("Aot", "IL2026")] + DataTable Act() => this.testObjects.ToDataTable(t => t.AString); + + var dt = Act(); + + Assert.AreEqual("AString", dt.Columns[0].Caption); + Assert.AreEqual(typeof(string), dt.Columns[0].DataType); + + Assert.AreEqual(1, dt.Columns.Count); + } + + [TestMethod] + public void ToDataTableWithSchema() + { + using var dt = new DataTable(); + var columns = dt.Columns; + _ = columns.Add("Column1", typeof(int)); + _ = columns.Add("Value", typeof(string)); + _ = columns.Add("Column3", typeof(int)); + _ = columns.Add("Name", typeof(string)); + + var vars = Environment.GetEnvironmentVariables() + .Cast() + .ToArray(); + + [UnconditionalSuppressMessage("Aot", "IL2026")] + void Act() => _ = vars.Select(e => new { Name = e.Key.ToString(), Value = e.Value!.ToString() }) + .ToDataTable(dt, e => e.Name, e => e.Value); + + Act(); + + var rows = dt.Rows.Cast().ToArray(); + Assert.AreEqual(vars.Length, rows.Length); + CollectionAssert.AreEqual(vars.Select(e => e.Key).ToArray(), rows.Select(r => r["Name"]).ToArray()); + CollectionAssert.AreEqual(vars.Select(e => e.Value).ToArray(), rows.Select(r => r["Value"]).ToArray()); + } + + readonly struct Point(int x, int y) + { +#pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649) + public static Point Empty = new(); +#pragma warning restore CA1805 // Do not initialize unnecessarily + public bool IsEmpty => X == 0 && Y == 0; + public int X { get; } = x; + public int Y { get; } = y; + } + + [TestMethod] + public void ToDataTableIgnoresStaticMembers() + { + [UnconditionalSuppressMessage("Aot", "IL2026")] + static DataTable Act() => new[] { new Point(12, 34) }.ToDataTable(); + + var points = Act(); + + Assert.AreEqual(3, points.Columns.Count); + var x = points.Columns["X"]; + var y = points.Columns["Y"]; + var empty = points.Columns["IsEmpty"]; + Assert.IsNotNull(x); + Assert.IsNotNull(y); + Assert.IsNotNull(empty); + var row = points.Rows.Cast().Single(); + Assert.AreEqual(12, row[x]); + Assert.AreEqual(34, row[y]); + Assert.AreEqual(row[empty], false); + } + } +} diff --git a/MoreLinq.Test/Throws.cs b/MoreLinq.Test/Throws.cs index 672234e3c..58877a6fc 100644 --- a/MoreLinq.Test/Throws.cs +++ b/MoreLinq.Test/Throws.cs @@ -36,16 +36,19 @@ public static ExactTypeConstraint TypeOf() NUnit.Framework.Throws.TypeOf(); public static EqualConstraint ArgumentException(string expectedParamName) => - NUnit.Framework.Throws.ArgumentException.With.ParamName().EqualTo(expectedParamName); + NUnit.Framework.Throws.ArgumentException.With.ParamName(expectedParamName); public static EqualConstraint ArgumentNullException(string expectedParamName) => - NUnit.Framework.Throws.ArgumentNullException.With.ParamName().EqualTo(expectedParamName); + NUnit.Framework.Throws.ArgumentNullException.With.ParamName(expectedParamName); public static ExactTypeConstraint ArgumentOutOfRangeException() => TypeOf(); public static EqualConstraint ArgumentOutOfRangeException(string expectedParamName) => - ArgumentOutOfRangeException().With.ParamName().EqualTo(expectedParamName); + ArgumentOutOfRangeException().With.ParamName(expectedParamName); + + public static EqualConstraint ParamName(this ConstraintExpression constraint, string expectedParamName) => + constraint.ParamName().EqualTo(expectedParamName); static ResolvableConstraintExpression ParamName(this ConstraintExpression constraint) => constraint.Property(nameof(System.ArgumentException.ParamName)); diff --git a/MoreLinq.Test/ToDataTableTest.cs b/MoreLinq.Test/ToDataTableTest.cs index d6cac4cda..65958f544 100644 --- a/MoreLinq.Test/ToDataTableTest.cs +++ b/MoreLinq.Test/ToDataTableTest.cs @@ -77,33 +77,35 @@ public void ToDataTableTableWithWrongColumnDataType() Throws.ArgumentException("table")); } + void TestDataTableMemberExpression(Expression> expression) + { + Assert.That(() => this.testObjects.ToDataTable(expression), + Throws.ArgumentException("expressions") + .And.InnerException.With.ParamName("lambda")); + } + [Test] public void ToDataTableMemberExpressionMethod() { - Assert.That(() => this.testObjects.ToDataTable(t => t.ToString()), - Throws.ArgumentException("lambda")); + TestDataTableMemberExpression(t => t.ToString()); } - [Test] public void ToDataTableMemberExpressionNonMember() { - Assert.That(() => this.testObjects.ToDataTable(t => t.ToString().Length), - Throws.ArgumentException("lambda")); + TestDataTableMemberExpression(t => t.ToString().Length); } [Test] public void ToDataTableMemberExpressionIndexer() { - Assert.That(() => this.testObjects.ToDataTable(t => t[0]), - Throws.ArgumentException("lambda")); + TestDataTableMemberExpression(t => t[0]); } [Test] public void ToDataTableMemberExpressionStatic() { - Assert.That(() => _ = this.testObjects.ToDataTable(_ => DateTime.Now), - Throws.ArgumentException("lambda")); + TestDataTableMemberExpression(_ => DateTime.Now); } [Test] @@ -113,12 +115,6 @@ public void ToDataTableSchemaInDeclarationOrder() // Assert properties first, then fields, then in declaration order - Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString")); - Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string))); - - Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal")); - Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal))); - Assert.That(dt.Columns[2].Caption, Is.EqualTo("KeyField")); Assert.That(dt.Columns[2].DataType, Is.EqualTo(typeof(int))); @@ -126,6 +122,12 @@ public void ToDataTableSchemaInDeclarationOrder() Assert.That(dt.Columns[3].DataType, Is.EqualTo(typeof(Guid))); Assert.That(dt.Columns[3].AllowDBNull, Is.True); + Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString")); + Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string))); + + Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal")); + Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal))); + Assert.That(dt.Columns.Count, Is.EqualTo(4)); } diff --git a/MoreLinq.sln b/MoreLinq.sln index ff1b65d6c..d37b3cefb 100644 --- a/MoreLinq.sln +++ b/MoreLinq.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.Test", "MoreLinq.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.ExtensionsGenerator", "bld\ExtensionsGenerator\MoreLinq.ExtensionsGenerator.csproj", "{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoreLinq.Test.Aot", "MoreLinq.Test.Aot\MoreLinq.Test.Aot.csproj", "{776973A3-AC2E-423E-8106-B4E296CE7752}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +45,10 @@ Global {5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.Build.0 = Release|Any CPU + {776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.Build.0 = Debug|Any CPU + {776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.ActiveCfg = Release|Any CPU + {776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index 20380243d..bf5c9ef7b 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -43,7 +43,6 @@ namespace MoreLinq.Extensions [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] public static partial class ToDataTableExtension { - /// /// Converts a sequence to a object. /// @@ -54,8 +53,11 @@ public static partial class ToDataTableExtension /// /// This operator uses immediate execution. - public static DataTable ToDataTable(this IEnumerable source) - => MoreEnumerable.ToDataTable(source); + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + public static DataTable + ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T>( + this IEnumerable source) + => MoreEnumerable. ToDataTable(source); /// /// Appends elements in the sequence as rows of a given @@ -70,8 +72,10 @@ public static DataTable ToDataTable(this IEnumerable source) /// /// This operator uses immediate execution. + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) => MoreEnumerable.ToDataTable(source, expressions); + /// /// Appends elements in the sequence as rows of a given object. /// @@ -84,9 +88,12 @@ public static DataTable ToDataTable(this IEnumerable source, params Expres /// /// This operator uses immediate execution. - public static TTable ToDataTable(this IEnumerable source, TTable table) + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + public static TTable + ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T, + TTable>(this IEnumerable source, TTable table) where TTable : DataTable - => MoreEnumerable.ToDataTable(source, table); + => MoreEnumerable. ToDataTable(source, table); /// /// Appends elements in the sequence as rows of a given @@ -103,6 +110,7 @@ public static TTable ToDataTable(this IEnumerable source, TTable t /// /// This operator uses immediate execution. + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) where TTable : DataTable => MoreEnumerable.ToDataTable(source, table, expressions); diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 6c1c604b5..8604ca538 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -126,6 +126,7 @@ true MoreLinq Library + true key.snk true true @@ -170,9 +171,13 @@ System.Index; System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute; + System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute; System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute; System.Diagnostics.CodeAnalysis.MemberNotNullAttribute; System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute; + System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute; System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; @@ -211,6 +216,10 @@ $(DefineConstants);NO_ASYNC_STREAMS;NO_BUFFERS + + $(DefineConstants);DYNAMIC_CODE_FALLBACK + + diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index ba2275956..1872c3d85 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -20,61 +20,83 @@ namespace MoreLinq using System; using System.Collections.Generic; using System.Data; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; + using static Diagnostics; static partial class MoreEnumerable { /// - /// Appends elements in the sequence as rows of a given object. + /// Converts a sequence to a object. /// /// The type of the elements of . - /// /// The source. - /// /// - /// A or subclass representing the source. + /// A representing the source. /// /// This operator uses immediate execution. - public static TTable ToDataTable(this IEnumerable source, TTable table) - where TTable : DataTable + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + public static DataTable + ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T>( + this IEnumerable source) { - return ToDataTable(source, table, []); + if (source == null) throw new ArgumentNullException(nameof(source)); + + return source.ToDataTable(new DataTable()); } /// - /// Appends elements in the sequence as rows of a given - /// object with a set of lambda expressions specifying which members (property - /// or field) of each element in the sequence will supply the column values. + /// Appends elements in the sequence as rows of a given object. /// /// The type of the elements of . + /// /// The source. - /// Expressions providing access to element members. + /// /// - /// A representing the source. + /// A or subclass representing the source. /// /// This operator uses immediate execution. - public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + public static TTable + ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedPublicPropertiesOrFields)] T, + TTable>(this IEnumerable source, TTable table) + where TTable : DataTable { - return ToDataTable(source, new DataTable(), expressions); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (table == null) throw new ArgumentNullException(nameof(table)); + + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + + var members = typeof(T).GetProperties(bindingFlags) + .Where(p => p.CanRead && p.GetIndexParameters().Length == 0) + .Cast() + .Concat(typeof(T).GetFields(bindingFlags)) + .ToArray(); + + return ToDataTable(source, table, members); } /// - /// Converts a sequence to a object. + /// Appends elements in the sequence as rows of a given + /// object with a set of lambda expressions specifying which members (property + /// or field) of each element in the sequence will supply the column values. /// /// The type of the elements of . /// The source. + /// Expressions providing access to element members. /// /// A representing the source. /// /// This operator uses immediate execution. - public static DataTable ToDataTable(this IEnumerable source) + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) { - return ToDataTable(source, new DataTable()); + return ToDataTable(source, new DataTable(), expressions); } /// @@ -92,6 +114,7 @@ public static DataTable ToDataTable(this IEnumerable source) /// /// This operator uses immediate execution. + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) where TTable : DataTable { @@ -99,7 +122,13 @@ public static TTable ToDataTable(this IEnumerable source, TTable t if (table == null) throw new ArgumentNullException(nameof(table)); if (expressions == null) throw new ArgumentNullException(nameof(expressions)); - var members = PrepareMemberInfos(expressions).ToArray(); + return ToDataTable(source, table, PrepareMemberInfos(expressions)); + } + + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + static TTable ToDataTable(IEnumerable source, TTable table, MemberInfo[] members) + where TTable : DataTable + { var boundMembers = BuildOrBindSchema(table, members); var shredder = CreateShredder(boundMembers); @@ -127,19 +156,10 @@ public static TTable ToDataTable(this IEnumerable source, TTable t return table; } - static IEnumerable PrepareMemberInfos(ICollection>> expressions) + static MemberInfo[] PrepareMemberInfos(ICollection>> expressions) { - // - // If no lambda expressions supplied then reflect them off the source element type. - // - if (expressions.Count == 0) - { - return from m in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance) - where m.MemberType == MemberTypes.Field - || m is PropertyInfo { CanRead: true } p && p.GetIndexParameters().Length == 0 - select m; - } + return []; // // Ensure none of the expressions is null. @@ -149,7 +169,7 @@ static IEnumerable PrepareMemberInfos(ICollection + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] static MemberInfo?[] BuildOrBindSchema(DataTable table, MemberInfo[] members) { // @@ -224,8 +245,48 @@ var type when Nullable.GetUnderlyingType(type) is { } ut => ut, }; } - static Func CreateShredder(MemberInfo?[] members) + [UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode", + Justification = "Falls back to reflection-based member access at run-time if the CLR " + + "version does not support dynamic code generation.")] + static Func CreateShredder(MemberInfo?[] members) { +#if DYNAMIC_CODE_FALLBACK + + // + // If the runtime does not support dynamic code generation, then + // fall back to reflection-based member access at run-time. + // + // See also: https://github.com/dotnet/runtime/issues/17973#issuecomment-1330799386 + // + + if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported) + { + return obj => + { + var values = new object?[members.Length]; + + for (var i = 0; i < members.Length; i++) + { + var member = members[i]; + values[i] = member switch + { + null => null, + PropertyInfo pi => pi.GetValue(obj), + FieldInfo fi => fi.GetValue(obj), + _ => throw new UnreachableException(), + }; + } + + return values; + }; + } +#endif + + // + // Otherwise compile a lambda expression that will extract the + // values of the specified members from an object instance. + // + var parameter = Expression.Parameter(typeof(T), "e"); // @@ -240,7 +301,7 @@ static Func CreateShredder(MemberInfo?[] members) var array = Expression.NewArrayInit(typeof(object), initializers); - var lambda = Expression.Lambda>(array, parameter); + var lambda = Expression.Lambda>(array, parameter); return lambda.Compile(); @@ -251,4 +312,27 @@ UnaryExpression CreateMemberAccessor(MemberInfo member) } } } + + namespace Extensions + { + partial class ToDataTableExtension + { + internal const DynamicallyAccessedMemberTypes DynamicallyAccessedPublicPropertiesOrFields + = DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.PublicFields; + + internal const string RequiresUnreferencedCodeMessage = + "This method uses reflection to access public properties and fields of the source " + + "type, and in turn the types of those properties and fields. That latter could be " + + "problematic and require root descriptors for some custom and complex types " + + "(although columns usually store simple, scalar types). For more, see: " + + "https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#root-descriptors"; + } + } + + file static class Diagnostics + { + public const DynamicallyAccessedMemberTypes DynamicallyAccessedPublicPropertiesOrFields = MoreLinq.Extensions.ToDataTableExtension.DynamicallyAccessedPublicPropertiesOrFields; + public const string RequiresUnreferencedCodeMessage = MoreLinq.Extensions.ToDataTableExtension.RequiresUnreferencedCodeMessage; + } } diff --git a/test.cmd b/test.cmd index a6d28730d..209719ca3 100644 --- a/test.cmd +++ b/test.cmd @@ -7,6 +7,10 @@ popd & exit /b %ERRORLEVEL% setlocal if not defined SKIP_TEST_BUILD set SKIP_TEST_BUILD=false if %SKIP_TEST_BUILD%==false call build || exit /b 1 +if not "%~1"=="aot" goto :test-all +call :test-aot +exit /b %ERRORLEVEL% +:test-all call :clean ^ && call :test net8.0 Debug ^ && call :test net8.0 Release ^ @@ -14,7 +18,8 @@ call :clean ^ && call :test net6.0 Release ^ && call :test net471 Debug ^ && call :test net471 Release ^ - && call :report-cover + && call :report-cover ^ + && call :test-aot exit /b %ERRORLEVEL% :clean @@ -51,3 +56,17 @@ dotnet reportgenerator -reports:coverage-*.opencover.xml ^ -targetdir:reports ^ && type reports\Summary.txt exit /b %ERRORLEVEL% + +:test-aot +setlocal +cd MoreLinq.Test.Aot +dotnet publish +if not ERRORLEVEL==0 exit /b %ERRORLEVEL% +set AOT_TEST_PUBLISH_DIR= +for /f %%d in ('dir /ad /s /b publish') do if not defined AOT_TEST_PUBLISH_DIR set AOT_TEST_PUBLISH_DIR=%%~d +if not defined AOT_TEST_PUBLISH_DIR ( + echo>&2 Published binary directory not found! + exit /b 1 +) +"%AOT_TEST_PUBLISH_DIR%\MoreLinq.Test.Aot.exe" +exit /b %ERRORLEVEL% diff --git a/test.sh b/test.sh index 9bd9806a0..8427d1849 100755 --- a/test.sh +++ b/test.sh @@ -31,3 +31,5 @@ else mono MoreLinq.Test/bin/$c/net471/MoreLinq.Test.exe done fi +dotnet publish MoreLinq.Test.Aot +"$(find MoreLinq.Test.Aot -type d -name publish)/MoreLinq.Test.Aot" From 9c130ff557657f26132c6f03961b0454029fecf7 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Sat, 6 Jul 2024 18:35:28 +0200 Subject: [PATCH 6/6] Bump version to 4.3 --- MoreLinq/MoreLinq.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 8604ca538..8264eee51 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -119,7 +119,7 @@ $([System.Text.RegularExpressions.Regex]::Replace($(Copyright), `\s+`, ` `).Trim()) MoreLINQ en-US - 4.2.0 + 4.3.0 MoreLINQ Developers. netstandard2.0;netstandard2.1;net6.0;net8.0 portable