From c9aa710f68c43c0e86b7efa14e5aa8d28b0cc2de Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 09:00:00 +0100 Subject: [PATCH 01/21] Add TestInterleaveDoNoCallMoveNextEagerly. And it does. --- MoreLinq.Test/InterleaveTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 598c3bd05..627dd0b7d 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -35,6 +35,23 @@ public void TestInterleaveIsLazy() new BreakingSequence().Interleave(new BreakingSequence()); } + /// + /// Verify that interleaving do not call enumerators MoveNext method eagerly + /// + [Test] + public void TestInterleaveDoNoCallMoveNextEagerly() + { + void Code() + { + var sequenceA = Enumerable.Range(1, 1); + var sequenceB = MoreEnumerable.From(() => throw new TestException()); + + sequenceA.Interleave(sequenceB).Take(1).Consume(); + } + + Assert.DoesNotThrow(Code); + } + /// /// Verify that interleaving disposes those enumerators that it managed /// to open successfully From 66d1228bf30b3597a87f403fda247b4027d74e6d Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 09:54:09 +0100 Subject: [PATCH 02/21] Fix interleave implementation so it doen't call MoveNext eagerly. --- MoreLinq/Interleave.cs | 150 ++++++++++++----------------------------- 1 file changed, 42 insertions(+), 108 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index cd19e19a6..0c29c8bfc 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -19,8 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; public static partial class MoreEnumerable { @@ -45,127 +43,63 @@ public static partial class MoreEnumerable /// A sequence of interleaved elements from all of the source sequences public static IEnumerable Interleave(this IEnumerable sequence, params IEnumerable[] otherSequences) - { - return Interleave(sequence, ImbalancedInterleaveStrategy.Skip, otherSequences); - } - - /// - /// Interleaves the elements of two or more sequences into a single sequence, applying the specified strategy when sequences are of unequal length - /// - /// - /// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed - /// by the second, then the third, and so on. So, for example:
- /// { 1,2,3,1,2,3,1,2,3 } - /// ]]> - /// This operator behaves in a deferred and streaming manner.
- /// When sequences are of unequal length, this method will use the imbalance strategy specified to - /// decide how to continue interleaving the remaining sequences. See - /// for more information.
- /// The sequences are interleaved in the order that they appear in the - /// collection, with as the first sequence. - ///
- /// The type of the elements of the source sequences - /// The first sequence in the interleave group - /// Defines the behavior of the operator when sequences are of unequal length - /// The other sequences in the interleave group - /// A sequence of interleaved elements from all of the source sequences - - static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInterleaveStrategy imbalanceStrategy, params IEnumerable[] otherSequences) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); - if (otherSequences.Any(s => s == null)) - throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); - return _(); IEnumerable _() - { - var sequences = new[] { sequence }.Concat(otherSequences); + return InterleaveSkip(otherSequences.Prepend(sequence)); + } - // produce an iterator collection for all IEnumerable instancess passed to us - var iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); - List> iteratorList = null; + private static IEnumerable InterleaveSkip(this IEnumerable> sequences) + { + var enumerators = new LinkedList>(); - try + try + { + // First pass. create enumerators. + foreach (var sequence in sequences) { - iteratorList = new List>(iterators); - iterators = null; - var shouldContinue = true; - var consumedIterators = 0; - var iterCount = iteratorList.Count; - - while (shouldContinue) - { - // advance every iterator and verify a value exists to be yielded - for (var index = 0; index < iterCount; index++) - { - if (!iteratorList[index].MoveNext()) - { - // check if all iterators have been consumed and we can terminate - // or if the imbalance strategy informs us that we MUST terminate - if (++consumedIterators == iterCount || imbalanceStrategy == ImbalancedInterleaveStrategy.Stop) - { - shouldContinue = false; - break; - } + if (sequence == null) + throw new ArgumentException("An item is null.", nameof(sequences)); - iteratorList[index].Dispose(); // dispose the iterator sice we no longer need it + var enumerator = sequence.GetEnumerator(); - // otherwise, apply the imbalance strategy - switch (imbalanceStrategy) - { - case ImbalancedInterleaveStrategy.Pad: - var newIter = iteratorList[index] = Generate(default(T), x => default).GetEnumerator(); - newIter.MoveNext(); - break; + // Immediately dispose enumerators of empty sequences. + if (!enumerator.MoveNext()) + { + enumerator.Dispose(); + continue; + } - case ImbalancedInterleaveStrategy.Skip: - iteratorList.RemoveAt(index); // no longer visit this particular iterator - --iterCount; // reduce the expected number of iterators to visit - --index; // decrement iterator index to compensate for index shifting - --consumedIterators; // decrement consumer iterator count to stay in balance - break; - } + yield return enumerator.Current; + enumerators.AddLast(enumerator); + } - } - } + var node = enumerators.First; + while (node != null) + { + var nextNode = node.Next; - if (shouldContinue) // only if all iterators could be advanced - { - // yield the values of each iterator's current position - for (var index = 0; index < iterCount; index++) - { - yield return iteratorList[index].Current; - } - } + var enumerator = node.Value; + if (enumerator.MoveNext()) + { + yield return enumerator.Current; } - } - finally - { - Debug.Assert(iteratorList != null || iterators != null); - foreach (var iter in (iteratorList ?? (IList>) iterators)) - iter.Dispose(); + else + { + enumerator.Dispose(); + enumerators.Remove(node); + } + + // Work on next node or restart from first one. + node = nextNode ?? enumerators.First; } } - } - - /// - /// Defines the strategies available when Interleave is passed sequences of unequal length - /// - enum ImbalancedInterleaveStrategy - { - /// - /// Extends a sequence by padding its tail with default(T) - /// - Pad, - /// - /// Removes the sequence from the interleave set, and continues interleaving remaining sequences. - /// - Skip, - /// - /// Stops the interleave operation. - /// - Stop, + finally + { + foreach (var enumerator in enumerators) + enumerator.Dispose(); + } } } } From 336423acff063453da3196dd6357fe06376c3f8c Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 10:30:08 +0100 Subject: [PATCH 03/21] Update MoreLinq/Interleave.cs Remove useless extension method signature on a private method --- MoreLinq/Interleave.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 0c29c8bfc..80cfbf4f8 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -50,7 +50,7 @@ public static IEnumerable Interleave(this IEnumerable sequence, params return InterleaveSkip(otherSequences.Prepend(sequence)); } - private static IEnumerable InterleaveSkip(this IEnumerable> sequences) + private static IEnumerable InterleaveSkip(IEnumerable> sequences) { var enumerators = new LinkedList>(); From 61e49217d74020fbd57a4ae63747fc884e1d1c80 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 22 Nov 2019 11:22:27 +0100 Subject: [PATCH 04/21] Fix the test --- MoreLinq.Test/InterleaveTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 627dd0b7d..d981979b5 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -44,7 +44,7 @@ public void TestInterleaveDoNoCallMoveNextEagerly() void Code() { var sequenceA = Enumerable.Range(1, 1); - var sequenceB = MoreEnumerable.From(() => throw new TestException()); + var sequenceB = MoreEnumerable.From(() => 2, () => throw new TestException()); sequenceA.Interleave(sequenceB).Take(1).Consume(); } From b790e53103e24ea12a124efaa016ce7a9fa63f44 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 22 Nov 2019 11:23:59 +0100 Subject: [PATCH 05/21] Undo implementation to prove test isn't bokren --- MoreLinq/Interleave.cs | 150 +++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 42 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 80cfbf4f8..cd19e19a6 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -19,6 +19,8 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; public static partial class MoreEnumerable { @@ -44,62 +46,126 @@ public static partial class MoreEnumerable public static IEnumerable Interleave(this IEnumerable sequence, params IEnumerable[] otherSequences) { - if (sequence == null) throw new ArgumentNullException(nameof(sequence)); - if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); - - return InterleaveSkip(otherSequences.Prepend(sequence)); + return Interleave(sequence, ImbalancedInterleaveStrategy.Skip, otherSequences); } - private static IEnumerable InterleaveSkip(IEnumerable> sequences) + /// + /// Interleaves the elements of two or more sequences into a single sequence, applying the specified strategy when sequences are of unequal length + /// + /// + /// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed + /// by the second, then the third, and so on. So, for example:
+ /// { 1,2,3,1,2,3,1,2,3 } + /// ]]> + /// This operator behaves in a deferred and streaming manner.
+ /// When sequences are of unequal length, this method will use the imbalance strategy specified to + /// decide how to continue interleaving the remaining sequences. See + /// for more information.
+ /// The sequences are interleaved in the order that they appear in the + /// collection, with as the first sequence. + ///
+ /// The type of the elements of the source sequences + /// The first sequence in the interleave group + /// Defines the behavior of the operator when sequences are of unequal length + /// The other sequences in the interleave group + /// A sequence of interleaved elements from all of the source sequences + + static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInterleaveStrategy imbalanceStrategy, params IEnumerable[] otherSequences) { - var enumerators = new LinkedList>(); + if (sequence == null) throw new ArgumentNullException(nameof(sequence)); + if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); + if (otherSequences.Any(s => s == null)) + throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); - try + return _(); IEnumerable _() { - // First pass. create enumerators. - foreach (var sequence in sequences) - { - if (sequence == null) - throw new ArgumentException("An item is null.", nameof(sequences)); + var sequences = new[] { sequence }.Concat(otherSequences); + + // produce an iterator collection for all IEnumerable instancess passed to us + var iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); + List> iteratorList = null; - var enumerator = sequence.GetEnumerator(); + try + { + iteratorList = new List>(iterators); + iterators = null; + var shouldContinue = true; + var consumedIterators = 0; + var iterCount = iteratorList.Count; - // Immediately dispose enumerators of empty sequences. - if (!enumerator.MoveNext()) + while (shouldContinue) { - enumerator.Dispose(); - continue; - } + // advance every iterator and verify a value exists to be yielded + for (var index = 0; index < iterCount; index++) + { + if (!iteratorList[index].MoveNext()) + { + // check if all iterators have been consumed and we can terminate + // or if the imbalance strategy informs us that we MUST terminate + if (++consumedIterators == iterCount || imbalanceStrategy == ImbalancedInterleaveStrategy.Stop) + { + shouldContinue = false; + break; + } - yield return enumerator.Current; - enumerators.AddLast(enumerator); - } + iteratorList[index].Dispose(); // dispose the iterator sice we no longer need it - var node = enumerators.First; - while (node != null) - { - var nextNode = node.Next; + // otherwise, apply the imbalance strategy + switch (imbalanceStrategy) + { + case ImbalancedInterleaveStrategy.Pad: + var newIter = iteratorList[index] = Generate(default(T), x => default).GetEnumerator(); + newIter.MoveNext(); + break; - var enumerator = node.Value; - if (enumerator.MoveNext()) - { - yield return enumerator.Current; - } - else - { - enumerator.Dispose(); - enumerators.Remove(node); - } + case ImbalancedInterleaveStrategy.Skip: + iteratorList.RemoveAt(index); // no longer visit this particular iterator + --iterCount; // reduce the expected number of iterators to visit + --index; // decrement iterator index to compensate for index shifting + --consumedIterators; // decrement consumer iterator count to stay in balance + break; + } + + } + } - // Work on next node or restart from first one. - node = nextNode ?? enumerators.First; + if (shouldContinue) // only if all iterators could be advanced + { + // yield the values of each iterator's current position + for (var index = 0; index < iterCount; index++) + { + yield return iteratorList[index].Current; + } + } + } + } + finally + { + Debug.Assert(iteratorList != null || iterators != null); + foreach (var iter in (iteratorList ?? (IList>) iterators)) + iter.Dispose(); } } - finally - { - foreach (var enumerator in enumerators) - enumerator.Dispose(); - } + } + + /// + /// Defines the strategies available when Interleave is passed sequences of unequal length + /// + enum ImbalancedInterleaveStrategy + { + /// + /// Extends a sequence by padding its tail with default(T) + /// + Pad, + /// + /// Removes the sequence from the interleave set, and continues interleaving remaining sequences. + /// + Skip, + /// + /// Stops the interleave operation. + /// + Stop, } } } From 32f02e49ba4dd2a20fd85995f1c97c682089e590 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 09:00:00 +0100 Subject: [PATCH 06/21] Add TestInterleaveDoNoCallMoveNextEagerly. And it does. --- MoreLinq.Test/InterleaveTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 598c3bd05..627dd0b7d 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -35,6 +35,23 @@ public void TestInterleaveIsLazy() new BreakingSequence().Interleave(new BreakingSequence()); } + /// + /// Verify that interleaving do not call enumerators MoveNext method eagerly + /// + [Test] + public void TestInterleaveDoNoCallMoveNextEagerly() + { + void Code() + { + var sequenceA = Enumerable.Range(1, 1); + var sequenceB = MoreEnumerable.From(() => throw new TestException()); + + sequenceA.Interleave(sequenceB).Take(1).Consume(); + } + + Assert.DoesNotThrow(Code); + } + /// /// Verify that interleaving disposes those enumerators that it managed /// to open successfully From 6ff6d03d2f2d78de49a1de17b83ae9beb302054e Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 09:54:09 +0100 Subject: [PATCH 07/21] Fix interleave implementation so it doen't call MoveNext eagerly. --- MoreLinq/Interleave.cs | 148 ++++++++++++----------------------------- 1 file changed, 42 insertions(+), 106 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index c06a0129c..0c29c8bfc 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -19,8 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; public static partial class MoreEnumerable { @@ -45,125 +43,63 @@ public static partial class MoreEnumerable /// A sequence of interleaved elements from all of the source sequences public static IEnumerable Interleave(this IEnumerable sequence, params IEnumerable[] otherSequences) - { - return Interleave(sequence, ImbalancedInterleaveStrategy.Skip, otherSequences); - } - - /// - /// Interleaves the elements of two or more sequences into a single sequence, applying the specified strategy when sequences are of unequal length - /// - /// - /// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed - /// by the second, then the third, and so on. So, for example:
- /// { 1,2,3,1,2,3,1,2,3 } - /// ]]> - /// This operator behaves in a deferred and streaming manner.
- /// When sequences are of unequal length, this method will use the imbalance strategy specified to - /// decide how to continue interleaving the remaining sequences. See - /// for more information.
- /// The sequences are interleaved in the order that they appear in the - /// collection, with as the first sequence. - ///
- /// The type of the elements of the source sequences - /// The first sequence in the interleave group - /// Defines the behavior of the operator when sequences are of unequal length - /// The other sequences in the interleave group - /// A sequence of interleaved elements from all of the source sequences - - static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInterleaveStrategy imbalanceStrategy, params IEnumerable[] otherSequences) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); - if (otherSequences.Any(s => s == null)) - throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); - return _(); IEnumerable _() - { - var sequences = new[] { sequence }.Concat(otherSequences); + return InterleaveSkip(otherSequences.Prepend(sequence)); + } - // produce an iterator collection for all IEnumerable instancess passed to us - var iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); - List> iteratorList = null; + private static IEnumerable InterleaveSkip(this IEnumerable> sequences) + { + var enumerators = new LinkedList>(); - try + try + { + // First pass. create enumerators. + foreach (var sequence in sequences) { - iteratorList = new List>(iterators); - iterators = null; - var shouldContinue = true; - var consumedIterators = 0; - var iterCount = iteratorList.Count; - - while (shouldContinue) - { - // advance every iterator and verify a value exists to be yielded - for (var index = 0; index < iterCount; index++) - { - if (!iteratorList[index].MoveNext()) - { - // check if all iterators have been consumed and we can terminate - // or if the imbalance strategy informs us that we MUST terminate - if (++consumedIterators == iterCount || imbalanceStrategy == ImbalancedInterleaveStrategy.Stop) - { - shouldContinue = false; - break; - } + if (sequence == null) + throw new ArgumentException("An item is null.", nameof(sequences)); - iteratorList[index].Dispose(); // dispose the iterator sice we no longer need it + var enumerator = sequence.GetEnumerator(); - // otherwise, apply the imbalance strategy - switch (imbalanceStrategy) - { - case ImbalancedInterleaveStrategy.Pad: - var newIter = iteratorList[index] = Generate(default(T), x => default).GetEnumerator(); - newIter.MoveNext(); - break; + // Immediately dispose enumerators of empty sequences. + if (!enumerator.MoveNext()) + { + enumerator.Dispose(); + continue; + } - case ImbalancedInterleaveStrategy.Skip: - iteratorList.RemoveAt(index); // no longer visit this particular iterator - --iterCount; // reduce the expected number of iterators to visit - --index; // decrement iterator index to compensate for index shifting - --consumedIterators; // decrement consumer iterator count to stay in balance - break; - } + yield return enumerator.Current; + enumerators.AddLast(enumerator); + } - } - } + var node = enumerators.First; + while (node != null) + { + var nextNode = node.Next; - if (shouldContinue) // only if all iterators could be advanced - { - // yield the values of each iterator's current position - foreach (var iterator in iteratorList) - yield return iterator.Current; - } + var enumerator = node.Value; + if (enumerator.MoveNext()) + { + yield return enumerator.Current; } - } - finally - { - Debug.Assert(iteratorList != null || iterators != null); - foreach (var iter in (iteratorList ?? (IList>) iterators)) - iter.Dispose(); + else + { + enumerator.Dispose(); + enumerators.Remove(node); + } + + // Work on next node or restart from first one. + node = nextNode ?? enumerators.First; } } - } - - /// - /// Defines the strategies available when Interleave is passed sequences of unequal length - /// - enum ImbalancedInterleaveStrategy - { - /// - /// Extends a sequence by padding its tail with default(T) - /// - Pad, - /// - /// Removes the sequence from the interleave set, and continues interleaving remaining sequences. - /// - Skip, - /// - /// Stops the interleave operation. - /// - Stop, + finally + { + foreach (var enumerator in enumerators) + enumerator.Dispose(); + } } } } From 1a4270f99981e522ad14ac58232eba07582fba27 Mon Sep 17 00:00:00 2001 From: Orace Date: Fri, 22 Nov 2019 14:50:56 +0100 Subject: [PATCH 08/21] Interleave: add early test of null elements in otherSequences --- MoreLinq/Interleave.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 0c29c8bfc..d62c25452 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Linq; public static partial class MoreEnumerable { @@ -46,6 +47,8 @@ public static IEnumerable Interleave(this IEnumerable sequence, params { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); + if (otherSequences.Any(s => s == null)) + throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); return InterleaveSkip(otherSequences.Prepend(sequence)); } @@ -59,20 +62,18 @@ private static IEnumerable InterleaveSkip(this IEnumerable> // First pass. create enumerators. foreach (var sequence in sequences) { - if (sequence == null) - throw new ArgumentException("An item is null.", nameof(sequences)); - var enumerator = sequence.GetEnumerator(); // Immediately dispose enumerators of empty sequences. - if (!enumerator.MoveNext()) + if (enumerator.MoveNext()) + { + yield return enumerator.Current; + enumerators.AddLast(enumerator); + } + else { enumerator.Dispose(); - continue; } - - yield return enumerator.Current; - enumerators.AddLast(enumerator); } var node = enumerators.First; From 76e3516f1c8c5289a7fa6d70a14249bbe314a352 Mon Sep 17 00:00:00 2001 From: Orace Date: Fri, 22 Nov 2019 15:00:06 +0100 Subject: [PATCH 09/21] Added TestInterleaveEarlyThrowOnNullElementInOtherSequences --- MoreLinq.Test/InterleaveTest.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 627dd0b7d..d58fe77d5 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -18,6 +18,7 @@ namespace MoreLinq.Test { using System; + using System.Collections.Generic; using NUnit.Framework; /// @@ -35,6 +36,24 @@ public void TestInterleaveIsLazy() new BreakingSequence().Interleave(new BreakingSequence()); } + /// + /// Verify that Interleave early throw ArgumentNullException when an element + /// of otherSequences is null. + /// + [Test] + public void TestInterleaveEarlyThrowOnNullElementInOtherSequences() + { + void Code() + { + var sequenceA = Enumerable.Range(1, 1); + var otherSequences = new IEnumerable[] {null}; + + sequenceA.Interleave(otherSequences); + } + + Assert.Throws(Code); + } + /// /// Verify that interleaving do not call enumerators MoveNext method eagerly /// From 6f617609bcffd60fb5b1825f79eca5daf78fbcea Mon Sep 17 00:00:00 2001 From: Orace Date: Fri, 22 Nov 2019 15:37:09 +0100 Subject: [PATCH 10/21] Remove useless private extension method --- MoreLinq/Interleave.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index d62c25452..d5c57d845 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -53,7 +53,7 @@ public static IEnumerable Interleave(this IEnumerable sequence, params return InterleaveSkip(otherSequences.Prepend(sequence)); } - private static IEnumerable InterleaveSkip(this IEnumerable> sequences) + private static IEnumerable InterleaveSkip(IEnumerable> sequences) { var enumerators = new LinkedList>(); From 81479fb3c25091ada7ee4460fa6cb65226892d87 Mon Sep 17 00:00:00 2001 From: Orace Date: Mon, 25 Nov 2019 11:24:39 +0100 Subject: [PATCH 11/21] Added TestInterleaveDisposesOnPartialEnumeration --- MoreLinq.Test/InterleaveTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index d58fe77d5..5945ddf44 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -85,6 +85,24 @@ public void TestInterleaveDisposesOnError() } } + /// + /// Verify that, in case of partial enumeration, interleaving disposes those + /// enumerators that it managed to open successfully + /// + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestInterleaveDisposesOnPartialEnumeration(int count) + { + using var sequenceA = TestingSequence.Of(1); + using var sequenceB = TestingSequence.Of(2); + using var sequenceC = TestingSequence.Of(3); + + sequenceA.Interleave(sequenceB, sequenceC).Take(count).Consume(); + + } + /// /// Verify that two balanced sequences will interleave all of their elements /// From 00ef3661ce0d6ff4e7b9ec5f1f14563f438f9457 Mon Sep 17 00:00:00 2001 From: Orace Date: Mon, 25 Nov 2019 11:25:34 +0100 Subject: [PATCH 12/21] Made TestInterleaveDisposesOnPartialEnumeration pass --- MoreLinq/Interleave.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index d5c57d845..263fbc445 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -64,14 +64,14 @@ private static IEnumerable InterleaveSkip(IEnumerable> sequ { var enumerator = sequence.GetEnumerator(); - // Immediately dispose enumerators of empty sequences. if (enumerator.MoveNext()) { - yield return enumerator.Current; enumerators.AddLast(enumerator); + yield return enumerator.Current; } else { + // Immediately dispose enumerators of empty sequences. enumerator.Dispose(); } } From d70974b3997db04852657c8994eb131641de6edd Mon Sep 17 00:00:00 2001 From: Orace Date: Mon, 25 Nov 2019 11:44:50 +0100 Subject: [PATCH 13/21] Interleave: simple implementation using Acquire. --- MoreLinq/Interleave.cs | 70 +++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 263fbc445..acb50e748 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -50,56 +50,42 @@ public static IEnumerable Interleave(this IEnumerable sequence, params if (otherSequences.Any(s => s == null)) throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); - return InterleaveSkip(otherSequences.Prepend(sequence)); - } + return _(); IEnumerable _() + { + var sequences = new[] {sequence}.Concat(otherSequences); - private static IEnumerable InterleaveSkip(IEnumerable> sequences) - { - var enumerators = new LinkedList>(); + // produce an enumerators collection for all IEnumerable instances passed to us + var enumerators = sequences.Select(e => e.GetEnumerator()).Acquire(); - try - { - // First pass. create enumerators. - foreach (var sequence in sequences) + try { - var enumerator = sequence.GetEnumerator(); - - if (enumerator.MoveNext()) + var allNull = false; + while (!allNull) { - enumerators.AddLast(enumerator); - yield return enumerator.Current; - } - else - { - // Immediately dispose enumerators of empty sequences. - enumerator.Dispose(); - } - } + allNull = true; + for (var i = 0; i < enumerators.Length; i++) + { + var enumerator = enumerators[i]; + if (enumerator == null) + continue; - var node = enumerators.First; - while (node != null) - { - var nextNode = node.Next; + if (!enumerator.MoveNext()) + { + enumerator.Dispose(); + enumerators[i] = null; + continue; + } - var enumerator = node.Value; - if (enumerator.MoveNext()) - { - yield return enumerator.Current; + allNull = false; + yield return enumerator.Current; + } } - else - { - enumerator.Dispose(); - enumerators.Remove(node); - } - - // Work on next node or restart from first one. - node = nextNode ?? enumerators.First; } - } - finally - { - foreach (var enumerator in enumerators) - enumerator.Dispose(); + finally + { + foreach (var enumerator in enumerators) + enumerator?.Dispose(); + } } } } From 9be11addedd6dc7dbe80f88764e57b6b75569c8c Mon Sep 17 00:00:00 2001 From: Orace Date: Mon, 25 Nov 2019 11:50:16 +0100 Subject: [PATCH 14/21] remove trailing whitespaces --- MoreLinq.Test/InterleaveTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 5945ddf44..9e91c67d7 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -100,7 +100,6 @@ public void TestInterleaveDisposesOnPartialEnumeration(int count) using var sequenceC = TestingSequence.Of(3); sequenceA.Interleave(sequenceB, sequenceC).Take(count).Consume(); - } /// From 7d0aa0d474616ff65bc068b4b4c1322d1611b298 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 08:55:33 +0100 Subject: [PATCH 15/21] Removed TestInterleaveEarlyThrowOnNullElementInOtherSequences --- MoreLinq.Test/InterleaveTest.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 9e91c67d7..1200a2e43 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -36,24 +36,6 @@ public void TestInterleaveIsLazy() new BreakingSequence().Interleave(new BreakingSequence()); } - /// - /// Verify that Interleave early throw ArgumentNullException when an element - /// of otherSequences is null. - /// - [Test] - public void TestInterleaveEarlyThrowOnNullElementInOtherSequences() - { - void Code() - { - var sequenceA = Enumerable.Range(1, 1); - var otherSequences = new IEnumerable[] {null}; - - sequenceA.Interleave(otherSequences); - } - - Assert.Throws(Code); - } - /// /// Verify that interleaving do not call enumerators MoveNext method eagerly /// From 205c371ac1299a3d649d4294abcb8f8e735e0a5b Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 09:04:00 +0100 Subject: [PATCH 16/21] Niptick in Interleave --- MoreLinq/Interleave.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index acb50e748..677bd5510 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -52,7 +52,7 @@ public static IEnumerable Interleave(this IEnumerable sequence, params return _(); IEnumerable _() { - var sequences = new[] {sequence}.Concat(otherSequences); + var sequences = new[] { sequence }.Concat(otherSequences); // produce an enumerators collection for all IEnumerable instances passed to us var enumerators = sequences.Select(e => e.GetEnumerator()).Acquire(); @@ -69,15 +69,16 @@ public static IEnumerable Interleave(this IEnumerable sequence, params if (enumerator == null) continue; - if (!enumerator.MoveNext()) + if (enumerator.MoveNext()) + { + allNull = false; + yield return enumerator.Current; + } + else { enumerator.Dispose(); enumerators[i] = null; - continue; } - - allNull = false; - yield return enumerator.Current; } } } From 08e90e3b9c4c2fce49b40b2231cc78c701bd12e7 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 09:05:33 +0100 Subject: [PATCH 17/21] remove unused using in InterleaveTests --- MoreLinq.Test/InterleaveTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 1200a2e43..47b126da3 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -18,7 +18,6 @@ namespace MoreLinq.Test { using System; - using System.Collections.Generic; using NUnit.Framework; /// From 4fdb16ce419e6c61098c4c8d642ff68c2b1a6742 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 09:10:08 +0100 Subject: [PATCH 18/21] Interleave relace "allNull" by "hasNext" --- MoreLinq/Interleave.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 677bd5510..304563448 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -59,10 +59,10 @@ public static IEnumerable Interleave(this IEnumerable sequence, params try { - var allNull = false; - while (!allNull) + var hasNext = true; + while (hasNext) { - allNull = true; + hasNext = false; for (var i = 0; i < enumerators.Length; i++) { var enumerator = enumerators[i]; @@ -71,7 +71,7 @@ public static IEnumerable Interleave(this IEnumerable sequence, params if (enumerator.MoveNext()) { - allNull = false; + hasNext = true; yield return enumerator.Current; } else From ba740cc28da540685ec6fa10527ef97a4989dfb5 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 09:19:29 +0100 Subject: [PATCH 19/21] update own state first then call external parties --- MoreLinq/Interleave.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 304563448..164fec136 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -76,8 +76,8 @@ public static IEnumerable Interleave(this IEnumerable sequence, params } else { - enumerator.Dispose(); enumerators[i] = null; + enumerator.Dispose(); } } } From a7f2c6f48c0856725be8dea36f222931539bd3ad Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 09:24:50 +0100 Subject: [PATCH 20/21] Removed TestInterleaveDisposesOnPartialEnumeration --- MoreLinq.Test/InterleaveTest.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 47b126da3..627dd0b7d 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -66,23 +66,6 @@ public void TestInterleaveDisposesOnError() } } - /// - /// Verify that, in case of partial enumeration, interleaving disposes those - /// enumerators that it managed to open successfully - /// - [TestCase(0)] - [TestCase(1)] - [TestCase(2)] - [TestCase(3)] - public void TestInterleaveDisposesOnPartialEnumeration(int count) - { - using var sequenceA = TestingSequence.Of(1); - using var sequenceB = TestingSequence.Of(2); - using var sequenceC = TestingSequence.Of(3); - - sequenceA.Interleave(sequenceB, sequenceC).Take(count).Consume(); - } - /// /// Verify that two balanced sequences will interleave all of their elements /// From efd0f4cc6931efe3b0793555e72315b9eb571747 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 12 Dec 2019 10:51:18 +0100 Subject: [PATCH 21/21] Rewrite TestInterleaveDoNoCallMoveNextEagerly --- MoreLinq.Test/InterleaveTest.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index 627dd0b7d..35c3bd863 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -41,15 +41,10 @@ public void TestInterleaveIsLazy() [Test] public void TestInterleaveDoNoCallMoveNextEagerly() { - void Code() - { - var sequenceA = Enumerable.Range(1, 1); - var sequenceB = MoreEnumerable.From(() => throw new TestException()); - - sequenceA.Interleave(sequenceB).Take(1).Consume(); - } + var sequenceA = Enumerable.Range(1, 1); + var sequenceB = MoreEnumerable.From(() => throw new TestException()); - Assert.DoesNotThrow(Code); + sequenceA.Interleave(sequenceB).Take(1).Consume(); } ///