public void EvaluationBehavior(int count) { int index = 0; int limit = count * 2; var source = new DelegateIterator <int>( moveNext: () => index++ != limit, // Stop once we go past the limit. current: () => index, // Yield from 1 up to the limit, inclusive. dispose: () => index ^= int.MinValue); IEnumerator <int> iterator = source.TakeLast(count).GetEnumerator(); Assert.Equal(0, index); // Nothing should be done before MoveNext is called. for (int i = 1; i <= count; i++) { Assert.True(iterator.MoveNext()); Assert.Equal(count + i, iterator.Current); // After the first MoveNext call to the enumerator, everything should be evaluated and the enumerator // should be disposed. Assert.Equal(int.MinValue, index & int.MinValue); Assert.Equal(limit + 1, index & int.MaxValue); } Assert.False(iterator.MoveNext()); // Unlike SkipLast, TakeLast can tell straightaway that it can return a sequence with no elements if count <= 0. // The enumerable it returns is a specialized empty iterator that has no connections to the source. Hence, // after MoveNext returns false under those circumstances, it won't invoke Dispose on our enumerator. int expected = count <= 0 ? 0 : int.MinValue; Assert.Equal(expected, index & int.MinValue); }
public void DisposeAfterEnumeration(int sourceLength, int subLength) { int sourceState = 0; int subIndex = 0; // Index within the arrays the sub-collection is supposed to be at. int[] subState = new int[sourceLength]; bool sourceDisposed = false; bool[] subCollectionDisposed = new bool[sourceLength]; var source = new DelegateIterator <int>( moveNext: () => ++ sourceState <= sourceLength, current: () => 0, dispose: () => sourceDisposed = true); var subCollection = new DelegateIterator <int>( moveNext: () => ++ subState[subIndex] <= subLength, // Return true `subLength` times. current: () => subState[subIndex], dispose: () => subCollectionDisposed[subIndex++] = true); // Record that Dispose was called, and move on to the next index. var iterator = source.SelectMany(_ => subCollection); int index = 0; // How much have we gone into the iterator? IEnumerator <int> e = iterator.GetEnumerator(); using (e) { while (e.MoveNext()) { int item = e.Current; Assert.Equal(subState[subIndex], item); // Verify Current. Assert.Equal(index / subLength, subIndex); Assert.False(sourceDisposed); // Not yet. // This represents whehter the sub-collection we're iterating thru right now // has been disposed. Also not yet. Assert.False(subCollectionDisposed[subIndex]); // However, all of the sub-collections before us should have been disposed. // Their indices should also be maxed out. Assert.All(subState.Take(subIndex), s => Assert.Equal(subLength + 1, s)); Assert.All(subCollectionDisposed.Take(subIndex), t => Assert.True(t)); index++; } } Assert.True(sourceDisposed); Assert.Equal(sourceLength, subIndex); Assert.All(subState, s => Assert.Equal(subLength + 1, s)); Assert.All(subCollectionDisposed, t => Assert.True(t)); // Make sure the iterator's enumerator has been disposed properly. Assert.Equal(0, e.Current); // Default value. Assert.False(e.MoveNext()); Assert.Equal(0, e.Current); }
public void EvaluationBehavior(int count) { // We want to make sure no more than `count` items are ever evaluated ahead of the current position. // As an example, if Enumerable.Range(1, 6).SkipLast(2) is called, then we should read in the first 3 items, // yield 1, read in 4, yield 2, and so on. int index = 0; int limit = Math.Max(0, count * 2); var source = new DelegateIterator <int>( moveNext: () => index++ != limit, // Stop once we go past the limit. current: () => index, // Yield from 1 up to the limit, inclusive. dispose: () => index ^= int.MinValue); IEnumerator <int> iterator = source.SkipLast(count).GetEnumerator(); Assert.Equal(0, index); // Nothing should be done before MoveNext is called. for (int i = 1; i <= count; i++) { Assert.True(iterator.MoveNext()); Assert.Equal(i, iterator.Current); Assert.Equal(count + i, index); } Assert.False(iterator.MoveNext()); Assert.Equal(int.MinValue, index & int.MinValue); }
public void DisposeSource(int sourceCount, int count) { int state = 0; var source = new DelegateIterator <int>( moveNext: () => ++ state <= sourceCount, current: () => 0, dispose: () => state = -1); IEnumerator <int> iterator = source.Skip(count).GetEnumerator(); int iteratorCount = Math.Max(0, sourceCount - Math.Max(0, count)); Assert.All(Enumerable.Range(0, iteratorCount), _ => Assert.True(iterator.MoveNext())); Assert.False(iterator.MoveNext()); Assert.Equal(-1, state); }
public void DisposeSource(int sourceCount, int count) { int state = 0; var source = new DelegateIterator <int>( moveNext: () => ++ state <= sourceCount, current: () => 0, dispose: () => state = -1); IEnumerator <int> iterator = source.Take(count).GetEnumerator(); int iteratorCount = Math.Min(sourceCount, Math.Max(0, count)); Assert.All(Enumerable.Range(0, iteratorCount), _ => Assert.True(iterator.MoveNext())); Assert.False(iterator.MoveNext()); // Unlike Skip, Take can tell straightaway that it can return a sequence with no elements if count <= 0. // The enumerable it returns is a specialized empty iterator that has no connections to the source. Hence, // after MoveNext returns false under those circumstances, it won't invoke Dispose on our enumerator. int expected = count <= 0 ? 0 : -1; Assert.Equal(expected, state); }
public void DisposeAfterEnumeration(int sourceLength, int subLength) { int sourceState = 0; int subIndex = 0; // Index within the arrays the sub-collection is supposed to be at. int[] subState = new int[sourceLength]; bool sourceDisposed = false; bool[] subCollectionDisposed = new bool[sourceLength]; var source = new DelegateIterator <int>( moveNext: () => ++ sourceState <= sourceLength, current: () => 0, dispose: () => sourceDisposed = true); var subCollection = new DelegateIterator <int>( moveNext: () => ++ subState[subIndex] <= subLength, // Return true `subLength` times. current: () => subState[subIndex], dispose: () => subCollectionDisposed[subIndex++] = true); // Record that Dispose was called, and move on to the next index. var iterator = source.SelectMany(_ => subCollection); int index = 0; // How much have we gone into the iterator? IEnumerator <int> e = iterator.GetEnumerator(); using (e) { while (e.MoveNext()) { int item = e.Current; Assert.Equal(subState[subIndex], item); // Verify Current. Assert.Equal(index / subLength, subIndex); Assert.False(sourceDisposed); // Not yet. // This represents whehter the sub-collection we're iterating thru right now // has been disposed. Also not yet. Assert.False(subCollectionDisposed[subIndex]); // However, all of the sub-collections before us should have been disposed. // Their indices should also be maxed out. Assert.All(subState.Take(subIndex), s => Assert.Equal(subLength + 1, s)); Assert.All(subCollectionDisposed.Take(subIndex), t => Assert.True(t)); index++; } } Assert.True(sourceDisposed); Assert.Equal(sourceLength, subIndex); Assert.All(subState, s => Assert.Equal(subLength + 1, s)); Assert.All(subCollectionDisposed, t => Assert.True(t)); // .NET Core fixes an oversight where we wouldn't properly dispose // the SelectMany iterator. See https://github.com/dotnet/corefx/pull/13942. int expectedCurrent = PlatformDetection.IsFullFramework ? subLength : 0; Assert.Equal(expectedCurrent, e.Current); Assert.False(e.MoveNext()); Assert.Equal(expectedCurrent, e.Current); }