diff --git a/src/System.Linq/src/Resources/Strings.resx b/src/System.Linq/src/Resources/Strings.resx index 7c706b7926a2..33e13442c20d 100644 --- a/src/System.Linq/src/Resources/Strings.resx +++ b/src/System.Linq/src/Resources/Strings.resx @@ -72,4 +72,7 @@ Sequence contains no matching element - \ No newline at end of file + + Destination array is not long enough to copy all the items in the collection. Check array index and length. + + diff --git a/src/System.Linq/src/System/Linq/Partition.SpeedOpt.cs b/src/System.Linq/src/System/Linq/Partition.SpeedOpt.cs index 4f564bd4f527..2fa74ee83b5c 100644 --- a/src/System.Linq/src/System/Linq/Partition.SpeedOpt.cs +++ b/src/System.Linq/src/System/Linq/Partition.SpeedOpt.cs @@ -17,7 +17,7 @@ namespace System.Linq /// Returning an instance of this type is useful to quickly handle scenarios where it is known /// that an operation will result in zero elements. /// - internal sealed class EmptyPartition : IPartition, IEnumerator + internal sealed class EmptyPartition : IPartition, IEnumerator, IList, IReadOnlyList { /// /// A cached, immutable instance of an empty enumerable. @@ -28,6 +28,31 @@ private EmptyPartition() { } + public int Count => 0; + + public bool IsReadOnly => true; + + public TElement this[int index] + { + get => ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + set => ThrowHelper.ThrowNotSupportedException(); + } + + public void CopyTo(TElement[] array, int arrayIndex) + { + // Do nothing. + } + + public bool Contains(TElement item) => false; + + public int IndexOf(TElement item) => -1; + + void ICollection.Add(TElement item) => ThrowHelper.ThrowNotSupportedException(); + bool ICollection.Remove(TElement item) => ThrowHelper.ThrowNotSupportedException(); + void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); + void IList.Insert(int index, TElement item) => ThrowHelper.ThrowNotSupportedException(); + void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); + public IEnumerator GetEnumerator() => this; IEnumerator IEnumerable.GetEnumerator() => this; diff --git a/src/System.Linq/src/System/Linq/Range.SpeedOpt.cs b/src/System.Linq/src/System/Linq/Range.SpeedOpt.cs index b6437812c673..bb84e556b255 100644 --- a/src/System.Linq/src/System/Linq/Range.SpeedOpt.cs +++ b/src/System.Linq/src/System/Linq/Range.SpeedOpt.cs @@ -28,16 +28,7 @@ public int[] ToArray() return array; } - public List ToList() - { - List list = new List(_end - _start); - for (int cur = _start; cur != _end; cur++) - { - list.Add(cur); - } - - return list; - } + public List ToList() => new List(this); public int GetCount(bool onlyIfCheap) => unchecked(_end - _start); diff --git a/src/System.Linq/src/System/Linq/Range.cs b/src/System.Linq/src/System/Linq/Range.cs index a26ed82281e4..b056a1249044 100644 --- a/src/System.Linq/src/System/Linq/Range.cs +++ b/src/System.Linq/src/System/Linq/Range.cs @@ -28,7 +28,7 @@ public static IEnumerable Range(int start, int count) /// /// An iterator that yields a range of consecutive integers. /// - private sealed partial class RangeIterator : Iterator + private sealed partial class RangeIterator : Iterator, IList, IReadOnlyList { private readonly int _start; private readonly int _end; @@ -40,6 +40,61 @@ public RangeIterator(int start, int count) _end = unchecked(start + count); } + public int Count => unchecked(_end - _start); + + public bool IsReadOnly => true; + + public int this[int index] + { + get + { + if ((uint)index >= (uint)Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + return unchecked(index + _start); + } + set => ThrowHelper.ThrowNotSupportedException(); + } + + public void CopyTo(int[] array, int arrayIndex) + { + if (array is null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + if ((uint)arrayIndex >= (uint)array.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex); + + unchecked + { + if (array.Length - arrayIndex < Count) + ThrowHelper.ThrowArgumentArrayPlusOffTooSmall(); + + int end = arrayIndex + Count; + for (int index = arrayIndex, value = _start; index < end; index++, value++) + { + array[index] = value; + } + } + } + + public bool Contains(int item) => item >= _start && item < _end; + + public int IndexOf(int item) + { + if (item < _start || item >= _end) + { + return -1; + } + return unchecked(item - _start); + } + + void ICollection.Add(int item) => ThrowHelper.ThrowNotSupportedException(); + bool ICollection.Remove(int item) => ThrowHelper.ThrowNotSupportedException(); + void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); + void IList.Insert(int index, int item) => ThrowHelper.ThrowNotSupportedException(); + void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); + public override Iterator Clone() => new RangeIterator(_start, _end - _start); public override bool MoveNext() diff --git a/src/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs b/src/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs index 1764351bc831..4d393bd53b72 100644 --- a/src/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs +++ b/src/System.Linq/src/System/Linq/Repeat.SpeedOpt.cs @@ -24,16 +24,7 @@ public TResult[] ToArray() return array; } - public List ToList() - { - List list = new List(_count); - for (int i = 0; i != _count; ++i) - { - list.Add(_current); - } - - return list; - } + public List ToList() => new List(this); public int GetCount(bool onlyIfCheap) => _count; diff --git a/src/System.Linq/src/System/Linq/Repeat.cs b/src/System.Linq/src/System/Linq/Repeat.cs index 76e2ce4864d4..e9a4b8461a70 100644 --- a/src/System.Linq/src/System/Linq/Repeat.cs +++ b/src/System.Linq/src/System/Linq/Repeat.cs @@ -28,7 +28,7 @@ public static IEnumerable Repeat(TResult element, int count) /// An iterator that yields the same item multiple times. /// /// The type of the item. - private sealed partial class RepeatIterator : Iterator + private sealed partial class RepeatIterator : Iterator, IList, IReadOnlyList { private readonly int _count; @@ -39,6 +39,55 @@ public RepeatIterator(TResult element, int count) _count = count; } + public int Count => _count; + + public bool IsReadOnly => true; + + public TResult this[int index] + { + get + { + if ((uint)index >= (uint)_count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + return _current; + } + + set => ThrowHelper.ThrowNotSupportedException(); + } + + public void CopyTo(TResult[] array, int arrayIndex) + { + if (array is null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + if ((uint)arrayIndex >= (uint)array.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex); + + unchecked + { + if (array.Length - arrayIndex < Count) + ThrowHelper.ThrowArgumentArrayPlusOffTooSmall(); + + int end = arrayIndex + Count; + for (int index = arrayIndex; index < end; index++) + { + array[index] = _current; + } + } + } + + public bool Contains(TResult item) => EqualityComparer.Default.Equals(_current, item); + + public int IndexOf(TResult item) => EqualityComparer.Default.Equals(_current, item) ? 0 : -1; + + void ICollection.Add(TResult item) => ThrowHelper.ThrowNotSupportedException(); + bool ICollection.Remove(TResult item) => ThrowHelper.ThrowNotSupportedException(); + void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); + void IList.Insert(int index, TResult item) => ThrowHelper.ThrowNotSupportedException(); + void IList.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException(); + public override Iterator Clone() { return new RepeatIterator(_current, _count); diff --git a/src/System.Linq/src/System/Linq/ThrowHelper.cs b/src/System.Linq/src/System/Linq/ThrowHelper.cs index e39381b0cf68..a3b767935010 100644 --- a/src/System.Linq/src/System/Linq/ThrowHelper.cs +++ b/src/System.Linq/src/System/Linq/ThrowHelper.cs @@ -13,6 +13,10 @@ internal static class ThrowHelper internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) => throw new ArgumentOutOfRangeException(GetArgumentString(argument)); + internal static T ThrowArgumentOutOfRangeException(ExceptionArgument argument) => throw new ArgumentOutOfRangeException(GetArgumentString(argument)); + + internal static void ThrowArgumentArrayPlusOffTooSmall() => throw new ArgumentException(SR.ArrayPlusOffTooSmall); + internal static void ThrowMoreThanOneElementException() => throw new InvalidOperationException(SR.MoreThanOneElement); internal static void ThrowMoreThanOneMatchException() => throw new InvalidOperationException(SR.MoreThanOneMatch); @@ -23,6 +27,8 @@ internal static class ThrowHelper internal static void ThrowNotSupportedException() => throw new NotSupportedException(); + internal static T ThrowNotSupportedException() => throw new NotSupportedException(); + private static string GetArgumentString(ExceptionArgument argument) { switch (argument) @@ -44,6 +50,8 @@ private static string GetArgumentString(ExceptionArgument argument) case ExceptionArgument.second: return nameof(ExceptionArgument.second); case ExceptionArgument.selector: return nameof(ExceptionArgument.selector); case ExceptionArgument.source: return nameof(ExceptionArgument.source); + case ExceptionArgument.array: return nameof(ExceptionArgument.array); + case ExceptionArgument.arrayIndex: return nameof(ExceptionArgument.arrayIndex); default: Debug.Fail("The ExceptionArgument value is not defined."); return string.Empty; @@ -70,5 +78,7 @@ internal enum ExceptionArgument second, selector, source, + array, + arrayIndex, } } diff --git a/src/System.Linq/tests/EmptyPartitionTests.cs b/src/System.Linq/tests/EmptyPartitionTests.cs index 2596bb8cdced..72f2fdc6ac70 100644 --- a/src/System.Linq/tests/EmptyPartitionTests.cs +++ b/src/System.Linq/tests/EmptyPartitionTests.cs @@ -106,5 +106,78 @@ public void ResetIsNop() en.Reset(); en.Reset(); } + + [Fact] + public void ICollection_IsReadOnly() + { + var emptyPartition = GetEmptyPartition() as ICollection; + + Assert.Equal(true, emptyPartition.IsReadOnly); + Assert.Throws(() => emptyPartition.Add(0)); + Assert.Throws(() => emptyPartition.Remove(0)); + Assert.Throws(() => emptyPartition.Clear()); + } + + [Fact] + public void ICollection_Count() + { + var emptyPartition = GetEmptyPartition() as ICollection; + + Assert.Equal(0, emptyPartition.Count); + } + + [Fact] + public void ICollection_CopyTo_ProduceCorrectSequence() + { + var emptyPartition = GetEmptyPartition() as ICollection; + + var arrayIndex = 5; + var array = Enumerable.Range(0, 10).ToArray(); + emptyPartition.CopyTo(array, arrayIndex); + + for (var index = 0; index < 10; index++) + { + Assert.Equal(index, array[index]); + } + } + + [Fact] + public void ICollection_Contains() + { + var emptyPartition = GetEmptyPartition() as ICollection; + + Assert.Equal(false, emptyPartition.Contains(int.MinValue)); + Assert.Equal(false, emptyPartition.Contains(0)); + Assert.Equal(false, emptyPartition.Contains(int.MaxValue)); + } + + [Fact] + public void IList_IsReadOnly() + { + var emptyPartition = GetEmptyPartition() as IList; + + Assert.Throws(() => emptyPartition.Insert(0, 0)); + Assert.Throws(() => emptyPartition.RemoveAt(0)); + } + + [Fact] + public void IList_Indexer_ThrowExceptionOnOutOfRangeIndex() + { + var emptyPartition = GetEmptyPartition() as IList; + + AssertExtensions.Throws("index", () => emptyPartition[int.MinValue]); + AssertExtensions.Throws("index", () => emptyPartition[0]); + AssertExtensions.Throws("index", () => emptyPartition[int.MaxValue]); + } + + [Fact] + public void IList_IndexOf() + { + var emptyPartition = GetEmptyPartition() as IList; + + Assert.Equal(-1, emptyPartition.IndexOf(int.MinValue)); + Assert.Equal(-1, emptyPartition.IndexOf(0)); + Assert.Equal(-1, emptyPartition.IndexOf(int.MaxValue)); + } } } diff --git a/src/System.Linq/tests/RangeTests.cs b/src/System.Linq/tests/RangeTests.cs index ca23eec05d9b..3f1c7b52680e 100644 --- a/src/System.Linq/tests/RangeTests.cs +++ b/src/System.Linq/tests/RangeTests.cs @@ -225,5 +225,144 @@ public void LastOrDefault() { Assert.Equal(int.MaxValue - 101, Enumerable.Range(-100, int.MaxValue).LastOrDefault()); } + + [Fact] + public void ICollection_IsReadOnly() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + + Assert.Equal(true, rangeSequence.IsReadOnly); + Assert.Throws(() => rangeSequence.Add(0)); + Assert.Throws(() => rangeSequence.Remove(0)); + Assert.Throws(() => rangeSequence.Clear()); + } + + [Fact] + public void ICollection_Count() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + + Assert.Equal(100, rangeSequence.Count); + } + + [Fact] + public void ICollection_CopyTo_ProduceCorrectSequence() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + + var arrayIndex = 10; + var array = new int[rangeSequence.Count + arrayIndex]; + rangeSequence.CopyTo(array, arrayIndex); + + int expected = 0; + for (var index = arrayIndex; index < rangeSequence.Count + arrayIndex; index++) + { + expected++; + Assert.Equal(expected, array[index]); + } + + Assert.Equal(100, expected); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnNullArray() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + + AssertExtensions.Throws("array", () => rangeSequence.CopyTo(null, 0)); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnOutOfRangeArrayIndex() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + var array = new int[100]; + + AssertExtensions.Throws("arrayIndex", () => rangeSequence.CopyTo(array, int.MinValue)); + AssertExtensions.Throws("arrayIndex", () => rangeSequence.CopyTo(array, -1)); + AssertExtensions.Throws("arrayIndex", () => rangeSequence.CopyTo(array, 100)); + AssertExtensions.Throws("arrayIndex", () => rangeSequence.CopyTo(array, int.MaxValue)); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnArrayOverflow() + { + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + var array = new int[100]; + + Assert.Throws(() => rangeSequence.CopyTo(array, 1)); + } + + [Fact] + public void ICollection_Contains() + { + var emptyRangeSequence = Enumerable.Range(0, 0) as ICollection; + + Assert.Equal(false, emptyRangeSequence.Contains(int.MinValue)); + Assert.Equal(false, emptyRangeSequence.Contains(0)); + Assert.Equal(false, emptyRangeSequence.Contains(int.MaxValue)); + + var rangeSequence = Enumerable.Range(1, 100) as ICollection; + + Assert.Equal(false, rangeSequence.Contains(int.MinValue)); + Assert.Equal(false, rangeSequence.Contains(0)); + Assert.Equal(true, rangeSequence.Contains(1)); + Assert.Equal(true, rangeSequence.Contains(100)); + Assert.Equal(false, rangeSequence.Contains(101)); + Assert.Equal(false, rangeSequence.Contains(int.MaxValue)); + } + + [Fact] + public void IList_IsReadOnly() + { + var rangeSequence = Enumerable.Range(0, 100) as IList; + + Assert.Throws(() => rangeSequence.Insert(0, 0)); + Assert.Throws(() => rangeSequence.RemoveAt(0)); + } + + [Fact] + public void IList_Indexer_ProduceCorrectSequence() + { + var rangeSequence = Enumerable.Range(1, 100) as IList; + int expected = 0; + for (var index = 0; index < rangeSequence.Count; index++) + { + expected++; + Assert.Equal(expected, rangeSequence[index]); + } + + Assert.Equal(100, expected); + } + + [Fact] + public void IList_Indexer_ThrowExceptionOnOutOfRangeIndex() + { + var rangeSequence = Enumerable.Range(0, 100) as IList; + + AssertExtensions.Throws("index", () => rangeSequence[int.MinValue]); + AssertExtensions.Throws("index", () => rangeSequence[-1]); + AssertExtensions.Throws("index", () => rangeSequence[100]); + AssertExtensions.Throws("index", () => rangeSequence[int.MaxValue]); + } + + [Fact] + public void IList_IndexOf() + { + var emptyRangeSequence = Enumerable.Range(0, 0) as IList; + + Assert.Equal(-1, emptyRangeSequence.IndexOf(int.MinValue)); + Assert.Equal(-1, emptyRangeSequence.IndexOf(0)); + Assert.Equal(-1, emptyRangeSequence.IndexOf(int.MaxValue)); + + var rangeSequence = Enumerable.Range(1, 100) as IList; + + Assert.Equal(-1, rangeSequence.IndexOf(int.MinValue)); + Assert.Equal(-1, rangeSequence.IndexOf(0)); + Assert.Equal(0, rangeSequence.IndexOf(1)); + Assert.Equal(99, rangeSequence.IndexOf(100)); + Assert.Equal(-1, rangeSequence.IndexOf(101)); + Assert.Equal(-1, rangeSequence.IndexOf(int.MaxValue)); + } } } diff --git a/src/System.Linq/tests/RepeatTests.cs b/src/System.Linq/tests/RepeatTests.cs index 371f51535780..b0e39e30517c 100644 --- a/src/System.Linq/tests/RepeatTests.cs +++ b/src/System.Linq/tests/RepeatTests.cs @@ -240,5 +240,132 @@ public void Count() { Assert.Equal(42, Enumerable.Repeat("Test", 42).Count()); } + + [Fact] + public void ICollection_IsReadOnly() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + + Assert.Equal(true, repeatSequence.IsReadOnly); + Assert.Throws(() => repeatSequence.Add(null)); + Assert.Throws(() => repeatSequence.Remove(null)); + Assert.Throws(() => repeatSequence.Clear()); + } + + [Fact] + public void ICollection_Count() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + + Assert.Equal(100, repeatSequence.Count); + } + + [Fact] + public void ICollection_CopyTo_ProduceCorrectSequence() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + + var arrayIndex = 10; + var array = new string[repeatSequence.Count + arrayIndex]; + repeatSequence.CopyTo(array, arrayIndex); + + int count = 0; + for (var index = arrayIndex; index < repeatSequence.Count + arrayIndex; index++) + { + count++; + Assert.Equal("Test", array[index]); + } + + Assert.Equal(100, count); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnNullArray() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + + AssertExtensions.Throws("array", () => repeatSequence.CopyTo(null, 0)); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnOutOfRangeArrayIndex() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + var array = new string[100]; + + AssertExtensions.Throws("arrayIndex", () => repeatSequence.CopyTo(array, int.MinValue)); + AssertExtensions.Throws("arrayIndex", () => repeatSequence.CopyTo(array, -1)); + AssertExtensions.Throws("arrayIndex", () => repeatSequence.CopyTo(array, 100)); + AssertExtensions.Throws("arrayIndex", () => repeatSequence.CopyTo(array, int.MaxValue)); + } + + [Fact] + public void ICollection_CopyTo_ThrowExceptionOnArrayOverflow() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + var array = new string[100]; + + Assert.Throws(() => repeatSequence.CopyTo(array, 1)); + } + + [Fact] + public void ICollection_Contains() + { + var emptyRepeatSequence = Enumerable.Repeat("Test", 0) as IList; + + Assert.Equal(false, emptyRepeatSequence.Contains("Test")); + + var repeatSequence = Enumerable.Repeat("Test", 100) as ICollection; + + Assert.Equal(false, repeatSequence.Contains("AnotherTest")); + Assert.Equal(true, repeatSequence.Contains("Test")); + } + + [Fact] + public void IList_IsReadOnly() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as IList; + + Assert.Throws(() => repeatSequence.Insert(0, null)); + Assert.Throws(() => repeatSequence.RemoveAt(0)); + } + + [Fact] + public void IList_Indexer_ProduceCorrectSequence() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as IList; + int count = 0; + for (var index = 0; index < repeatSequence.Count; index++) + { + count++; + Assert.Equal("Test", repeatSequence[index]); + } + + Assert.Equal(100, count); + } + + [Fact] + public void IList_Indexer_ThrowExceptionOnOutOfRangeIndex() + { + var repeatSequence = Enumerable.Repeat("Test", 100) as IList; + + AssertExtensions.Throws("index", () => repeatSequence[int.MinValue]); + AssertExtensions.Throws("index", () => repeatSequence[-1]); + AssertExtensions.Throws("index", () => repeatSequence[100]); + AssertExtensions.Throws("index", () => repeatSequence[int.MaxValue]); + } + + [Fact] + public void IList_IndexOf() + { + var emptyRepeatSequence = Enumerable.Repeat("Test", 0) as IList; + + Assert.Equal(-1, emptyRepeatSequence.IndexOf("Test")); + + var repeatSequence = Enumerable.Repeat("Test", 100) as IList; + + Assert.Equal(-1, repeatSequence.IndexOf("AnotherTest")); + Assert.Equal(0, repeatSequence.IndexOf("Test")); + } } }