From b2a0e459245ec6bedee51724426515584fbe17b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 12 Nov 2025 17:43:39 +0000 Subject: [PATCH 01/11] Add List and related classes - Add unit tests. - Bump native assembly version. - Update nuspec --- .../GenericCollections/CollectionTestBase.cs | 138 +++ .../GenericCollections.nfproj | 51 ++ Tests/GenericCollections/ListTests.cs | 851 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 31 + Tests/GenericCollections/packages.config | 5 + Tests/GenericCollections/packages.lock.json | 19 + Tests/HashtableTests/HashtableTests.nfproj | 14 +- Tests/HashtableTests/packages.config | 4 +- Tests/HashtableTests/packages.lock.json | 12 +- Tests/QueueTests/QueueTests.nfproj | 14 +- Tests/QueueTests/packages.config | 4 +- Tests/QueueTests/packages.lock.json | 12 +- Tests/StackTests/StackTests.nfproj | 14 +- Tests/StackTests/packages.config | 4 +- Tests/StackTests/packages.lock.json | 12 +- nanoFramework.System.Collections.nuspec | 4 +- nanoFramework.System.Collections.sln | 9 + .../Collections/Generic/ICollection.cs | 66 ++ .../Generic/ICollectionDebugView.cs | 30 + .../Collections/Generic/IList.cs | 47 + .../Generic/IReadOnlyCollection.cs | 22 + .../Collections/Generic/IReadOnlyList.cs | 22 + .../Collections/Generic/List.cs | 776 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 +- .../nanoFramework.System.Collections.nfproj | 15 +- .../packages.config | 2 +- .../packages.lock.json | 6 +- 27 files changed, 2130 insertions(+), 56 deletions(-) create mode 100644 Tests/GenericCollections/CollectionTestBase.cs create mode 100644 Tests/GenericCollections/GenericCollections.nfproj create mode 100644 Tests/GenericCollections/ListTests.cs create mode 100644 Tests/GenericCollections/Properties/AssemblyInfo.cs create mode 100644 Tests/GenericCollections/packages.config create mode 100644 Tests/GenericCollections/packages.lock.json create mode 100644 nanoFramework.System.Collections/Collections/Generic/ICollection.cs create mode 100644 nanoFramework.System.Collections/Collections/Generic/ICollectionDebugView.cs create mode 100644 nanoFramework.System.Collections/Collections/Generic/IList.cs create mode 100644 nanoFramework.System.Collections/Collections/Generic/IReadOnlyCollection.cs create mode 100644 nanoFramework.System.Collections/Collections/Generic/IReadOnlyList.cs create mode 100644 nanoFramework.System.Collections/Collections/Generic/List.cs diff --git a/Tests/GenericCollections/CollectionTestBase.cs b/Tests/GenericCollections/CollectionTestBase.cs new file mode 100644 index 0000000..b30c8d4 --- /dev/null +++ b/Tests/GenericCollections/CollectionTestBase.cs @@ -0,0 +1,138 @@ +//using nanoFramework.TestFramework; +//using System; +//using System.Collections; + +//namespace GenericCollections +//{ +// public class CollectionTestBase +// { +// protected static readonly int[] s_intArray = new[] { -4, 5, -2, 3, 1, 2, -1, -3, 0, 4, -5, 3, 3 }; +// protected static readonly int[] s_excludedFromIntArray = new int[] { 100, -34, 42, int.MaxValue, int.MinValue }; + +// [Flags] +// protected enum IListApi +// { +// None = 0, +// IndexerGet = 0x1, +// IndexerSet = 0x2, +// Count = 0x4, +// IsReadOnly = 0x8, +// Clear = 0x10, +// Contains = 0x20, +// CopyTo = 0x40, +// GetEnumeratorGeneric = 0x80, +// IndexOf = 0x100, +// Insert = 0x200, +// RemoveAt = 0x400, +// GetEnumerator = 0x800, +// End +// } + +// protected class CallTrackingIList : IList +// { +// private IListApi _expectedApiCalls; +// private IListApi _calledMembers; + +// public CallTrackingIList(IListApi expectedApiCalls) +// { +// _expectedApiCalls = expectedApiCalls; +// } + +// public void AssertAllMembersCalled() +// { +// if (_expectedApiCalls != _calledMembers) +// { +// for (IListApi i = (IListApi)1; i < IListApi.End; i = (IListApi)((int)i << 1)) +// { +// Assert.Equal(_expectedApiCalls & i, _calledMembers & i); +// } +// } +// } + +// public T this[int index] +// { +// get +// { +// _calledMembers |= IListApi.IndexerGet; +// return default(T); +// } +// set +// { +// _calledMembers |= IListApi.IndexerSet; +// } +// } + +// public int Count +// { +// get +// { +// _calledMembers |= IListApi.Count; +// return 1; +// } +// } + +// public bool IsReadOnly +// { +// get +// { +// _calledMembers |= IListApi.IsReadOnly; +// return false; +// } +// } + +// public void Add(T item) +// { +// throw new NotImplementedException(); +// } + +// public void Clear() +// { +// _calledMembers |= IListApi.Clear; +// } + +// public bool Contains(T item) +// { +// _calledMembers |= IListApi.Contains; +// return false; +// } + +// public void CopyTo(T[] array, int arrayIndex) +// { +// _calledMembers |= IListApi.CopyTo; +// } + +// public IEnumerator GetEnumerator() +// { +// _calledMembers |= IListApi.GetEnumeratorGeneric; +// return null; +// } + +// public int IndexOf(T item) +// { +// _calledMembers |= IListApi.IndexOf; +// return -1; +// } + +// public void Insert(int index, T item) +// { +// _calledMembers |= IListApi.Insert; +// } + +// public bool Remove(T item) +// { +// throw new NotImplementedException(); +// } + +// public void RemoveAt(int index) +// { +// _calledMembers |= IListApi.RemoveAt; +// } + +// IEnumerator IEnumerable.GetEnumerator() +// { +// _calledMembers |= IListApi.GetEnumerator; +// return null; +// } +// } +// } +//} diff --git a/Tests/GenericCollections/GenericCollections.nfproj b/Tests/GenericCollections/GenericCollections.nfproj new file mode 100644 index 0000000..16c8655 --- /dev/null +++ b/Tests/GenericCollections/GenericCollections.nfproj @@ -0,0 +1,51 @@ + + + + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ + + + + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 2b275f71-e555-46bf-b1fe-c33d4cb06545 + Library + Properties + 512 + GenericCollections + NFUnitTest + False + true + UnitTest + v1.0 + true + true + + + + + + + + + + + + + + + + ..\..\packages\nanoFramework.CoreLibrary.2.0.0-preview.20\lib\netnano1.0\mscorlib.dll + + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.TestFramework.dll + + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.UnitTestLauncher.exe + + + + diff --git a/Tests/GenericCollections/ListTests.cs b/Tests/GenericCollections/ListTests.cs new file mode 100644 index 0000000..8dd92dc --- /dev/null +++ b/Tests/GenericCollections/ListTests.cs @@ -0,0 +1,851 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using nanoFramework.TestFramework; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace GenericCollections +{ + [TestClass] + public class ListTests + { + [TestMethod] + public void List_Constructor_Default() + { + var listInt = new List(); + Assert.IsNotNull(listInt); + Assert.AreEqual(0, listInt.Count); + + var listString = new List(); + Assert.IsNotNull(listString); + Assert.AreEqual(0, listString.Count); + + var listObject = new List(); + Assert.IsNotNull(listObject); + Assert.AreEqual(0, listObject.Count); + + var listClass = new List(); + Assert.IsNotNull(listClass); + Assert.AreEqual(0, listClass.Count); + } + + [TestMethod] + public void List_Constructor_Capacity() + { + var listInt = new List(10); + Assert.IsNotNull(listInt); + Assert.AreEqual(0, listInt.Count); + + var listString = new List(5); + Assert.IsNotNull(listString); + Assert.AreEqual(0, listString.Count); + + var listObject = new List(20); + Assert.IsNotNull(listObject); + Assert.AreEqual(0, listObject.Count); + + var listClass = new List(15); + Assert.IsNotNull(listClass); + Assert.AreEqual(0, listClass.Count); + } + + [TestMethod] + public void List_Constructor_IEnumerable() + { + int[] initialInt = new int[] { 1, 2, 3 }; + List listInt = new List(initialInt); + Assert.IsNotNull(listInt); + Assert.AreEqual(3, listInt.Count); + Assert.AreEqual(1, listInt[0]); + Assert.AreEqual(2, listInt[1]); + Assert.AreEqual(3, listInt[2]); + + string[] initialString = new string[] { "A", "B", "C" }; + List listString = new List(initialString); + Assert.IsNotNull(listString); + Assert.AreEqual(3, listString.Count); + Assert.AreEqual("A", listString[0]); + Assert.AreEqual("B", listString[1]); + Assert.AreEqual("C", listString[2]); + + DummyClass[] initialClass = new DummyClass[] + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + List listClass = new List(initialClass); + Assert.IsNotNull(listClass); + Assert.AreEqual(3, listClass.Count); + Assert.AreEqual(1, listClass[0].Id); + Assert.AreEqual("Two", listClass[1].Name); + Assert.AreEqual(3, listClass[2].Id); + } + + [TestMethod] + public void List_Add_And_Count() + { + var listInt = new List(); + listInt.Add(1); + listInt.Add(10); + Assert.AreEqual(2, listInt.Count); + + var listString = new List(); + listString.Add("Hello"); + listString.Add("World"); + Assert.AreEqual(2, listString.Count); + + var listObject = new List(); + listObject.Add(new object()); + listObject.Add(new object()); + Assert.AreEqual(2, listObject.Count); + + var listClass = new List(); + listClass.Add(new DummyClass(1, "Test1")); + listClass.Add(new DummyClass(2, "Test2")); + Assert.AreEqual(2, listClass.Count); + } + + [TestMethod] + public void List_Indexer_Get_And_Set() + { + var listInt = new List { 1, 2, 3 }; + Assert.AreEqual(1, listInt[0]); + Assert.AreEqual(2, listInt[1]); + Assert.AreEqual(3, listInt[2]); + listInt[1] = 20; + Assert.AreEqual(20, listInt[1]); + + var listString = new List { "A", "B", "C" }; + Assert.AreEqual("A", listString[0]); + Assert.AreEqual("B", listString[1]); + Assert.AreEqual("C", listString[2]); + listString[1] = "Z"; + Assert.AreEqual("Z", listString[1]); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + + Assert.AreEqual(1, listClass[0].Id); + Assert.AreEqual("Two", listClass[1].Name); + Assert.AreEqual(3, listClass[2].Id); + + listClass[1] = new DummyClass(20, "Twenty"); + Assert.AreEqual(20, listClass[1].Id); + Assert.AreEqual("Twenty", listClass[1].Name); + } + + [TestMethod] + public void List_Clear() + { + var listInt = new List { 1, 2, 3 }; + Assert.AreEqual(3, listInt.Count); + listInt.Clear(); + Assert.AreEqual(0, listInt.Count); + + var listString = new List { "A", "B", "C" }; + Assert.AreEqual(3, listString.Count); + listString.Clear(); + Assert.AreEqual(0, listString.Count); + + var listClass = new List + { + new(1, "One"), + new(2, "Two") + }; + + Assert.AreEqual(2, listClass.Count); + listClass.Clear(); + Assert.AreEqual(0, listClass.Count); + } + + [TestMethod] + public void List_Contains() + { + var listInt = new List { 1, 2, 3 }; + Assert.IsTrue(listInt.Contains(2)); + Assert.IsFalse(listInt.Contains(5)); + + var listString = new List { "A", "B", "C" }; + Assert.IsTrue(listString.Contains("B")); + Assert.IsFalse(listString.Contains("Z")); + + var listClass = new List + { + new(1, "One"), + new(2, "Two") + }; + var existingItem = listClass[0]; + var nonExistingItem = new DummyClass(3, "Three"); + Assert.IsTrue(listClass.Contains(existingItem)); + Assert.IsFalse(listClass.Contains(nonExistingItem)); + } + + [TestMethod] + public void List_Remove() + { + var listInt = new List { 1, 2, 3 }; + Assert.IsTrue(listInt.Remove(2)); + Assert.AreEqual(2, listInt.Count); + Assert.IsFalse(listInt.Remove(5)); + Assert.AreEqual(2, listInt.Count); + + var listString = new List { "A", "B", "C" }; + Assert.IsTrue(listString.Remove("B")); + Assert.AreEqual(2, listString.Count); + Assert.IsFalse(listString.Remove("Z")); + Assert.AreEqual(2, listString.Count); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + + var itemToRemove = listClass[1]; + Assert.IsTrue(listClass.Remove(itemToRemove)); + Assert.AreEqual(2, listClass.Count); + + var nonExistingItem = new DummyClass(4, "Four"); + Assert.IsFalse(listClass.Remove(nonExistingItem)); + Assert.AreEqual(2, listClass.Count); + } + + [TestMethod] + public void List_Insert() + { + var listInt = new List { 1, 3 }; + listInt.Insert(1, 2); + + Assert.AreEqual(3, listInt.Count); + Assert.AreEqual(1, listInt[0]); + Assert.AreEqual(2, listInt[1]); + Assert.AreEqual(3, listInt[2]); + + var listString = new List { "A", "C" }; + listString.Insert(1, "B"); + + Assert.AreEqual(3, listString.Count); + Assert.AreEqual("A", listString[0]); + Assert.AreEqual("B", listString[1]); + Assert.AreEqual("C", listString[2]); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + var newItem = new DummyClass(1, "One and a Half"); + listClass.Insert(1, newItem); + Assert.AreEqual(4, listClass.Count); + Assert.AreEqual(1, listClass[0].Id); + Assert.AreEqual("One and a Half", listClass[1].Name); + Assert.AreEqual(2, listClass[2].Id); + Assert.AreEqual(3, listClass[3].Id); + } + + [TestMethod] + public void List_IndexOf() + { + var listInt = new List { 1, 2, 3 }; + Assert.AreEqual(1, listInt.IndexOf(2)); + Assert.AreEqual(-1, listInt.IndexOf(5)); + + var listString = new List { "A", "B", "C" }; + Assert.AreEqual(1, listString.IndexOf("B")); + Assert.AreEqual(-1, listString.IndexOf("Z")); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + var existingItem = listClass[1]; + var nonExistingItem = new DummyClass(4, "Four"); + Assert.AreEqual(1, listClass.IndexOf(existingItem)); + Assert.AreEqual(-1, listClass.IndexOf(nonExistingItem)); + } + + [TestMethod] + public void List_RemoveAt() + { + var listInt = new List { 1, 2, 3 }; + listInt.RemoveAt(1); + Assert.AreEqual(2, listInt.Count); + Assert.AreEqual(1, listInt[0]); + Assert.AreEqual(3, listInt[1]); + + var listString = new List { "A", "B", "C" }; + listString.RemoveAt(1); + Assert.AreEqual(2, listString.Count); + Assert.AreEqual("A", listString[0]); + Assert.AreEqual("C", listString[1]); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + listClass.RemoveAt(1); + Assert.AreEqual(2, listClass.Count); + Assert.AreEqual(1, listClass[0].Id); + Assert.AreEqual(3, listClass[1].Id); + } + + [TestMethod] + public void List_ToArray() + { + var listInt = new List { 1, 2, 3 }; + var arrayInt = listInt.ToArray(); + Assert.AreEqual(3, arrayInt.Length); + Assert.AreEqual(1, arrayInt[0]); + Assert.AreEqual(2, arrayInt[1]); + Assert.AreEqual(3, arrayInt[2]); + + var listString = new List { "A", "B", "C" }; + var arrayString = listString.ToArray(); + Assert.AreEqual(3, arrayString.Length); + Assert.AreEqual("A", arrayString[0]); + Assert.AreEqual("B", arrayString[1]); + Assert.AreEqual("C", arrayString[2]); + + var listClass = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + var arrayClass = listClass.ToArray(); + Assert.AreEqual(3, arrayClass.Length); + Assert.AreEqual(1, arrayClass[0].Id); + Assert.AreEqual("Two", arrayClass[1].Name); + Assert.AreEqual(3, arrayClass[2].Id); + } + + [TestMethod] + public void List_Constructor_Capacity_Zero() + { + var list = new List(0); + Assert.IsNotNull(list); + Assert.AreEqual(0, list.Count); + + // Should still be able to add items + list.Add(1); + Assert.AreEqual(1, list.Count); + Assert.AreEqual(1, list[0]); + } + + [TestMethod] + public void List_Constructor_Capacity_Negative_ThrowsException() + { + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + _ = new List(-1); + }); + } + + [TestMethod] + public void List_Constructor_IEnumerable_Empty() + { + var emptyArray = new int[] { }; + var list = new List(emptyArray); + Assert.IsNotNull(list); + Assert.AreEqual(0, list.Count); + } + + [TestMethod] + public void List_Constructor_IEnumerable_Null_ThrowsException() + { + Assert.ThrowsException(typeof(ArgumentNullException), () => + { + var list = new List(null); + }); + } + + [TestMethod] + public void List_Indexer_Get_OutOfRange_ThrowsException() + { + var list = new List { 1, 2, 3 }; + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + _ = list[5]; + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + _ = list[-1]; + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + _ = list[3]; + }); + } + + [TestMethod] + public void List_Indexer_Set_OutOfRange_ThrowsException() + { + var list = new List { 1, 2, 3 }; + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list[5] = 10; + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list[-1] = 10; + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list[3] = 10; + }); + } + + [TestMethod] + public void List_Add_AfterClear() + { + var list = new List { 1, 2, 3 }; + list.Clear(); + list.Add(10); + Assert.AreEqual(1, list.Count); + Assert.AreEqual(10, list[0]); + + list.Add(20); + Assert.AreEqual(2, list.Count); + Assert.AreEqual(20, list[1]); + } + + [TestMethod] + public void List_Insert_AtBeginning() + { + var list = new List { 2, 3 }; + list.Insert(0, 1); + + Assert.AreEqual(3, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + Assert.AreEqual(3, list[2]); + } + + [TestMethod] + public void List_Insert_AtEnd() + { + var list = new List { 1, 2 }; + list.Insert(2, 3); + + Assert.AreEqual(3, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + Assert.AreEqual(3, list[2]); + } + + [TestMethod] + public void List_Insert_OutOfRange_ThrowsException() + { + var list = new List { 1, 2 }; + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.Insert(5, 10); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.Insert(-1, 10); + }); + } + + [TestMethod] + public void List_RemoveAt_OutOfRange_ThrowsException() + { + var list = new List { 1, 2, 3 }; + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.RemoveAt(5); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.RemoveAt(-1); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.RemoveAt(3); + }); + } + + [TestMethod] + public void List_RemoveAt_FirstElement() + { + var list = new List { 1, 2, 3 }; + list.RemoveAt(0); + + Assert.AreEqual(2, list.Count); + Assert.AreEqual(2, list[0]); + Assert.AreEqual(3, list[1]); + } + + [TestMethod] + public void List_RemoveAt_LastElement() + { + var list = new List { 1, 2, 3 }; + list.RemoveAt(2); + + Assert.AreEqual(2, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + } + + [TestMethod] + public void List_Contains_EmptyList() + { + var list = new List(); + Assert.IsFalse(list.Contains(1)); + + var listString = new List(); + Assert.IsFalse(listString.Contains("test")); + } + + [TestMethod] + public void List_Contains_NullValue() + { + var list = new List { "A", null, "B" }; + Assert.IsTrue(list.Contains(null)); + + var listWithoutNull = new List { "A", "B" }; + Assert.IsFalse(listWithoutNull.Contains(null)); + } + + [TestMethod] + public void List_IndexOf_MultipleOccurrences() + { + var list = new List { 1, 2, 3, 2, 4 }; + // Should return first occurrence + Assert.AreEqual(1, list.IndexOf(2)); + + var listString = new List { "A", "B", "C", "B", "D" }; + Assert.AreEqual(1, listString.IndexOf("B")); + } + + [TestMethod] + public void List_IndexOf_EmptyList() + { + var list = new List(); + Assert.AreEqual(-1, list.IndexOf(1)); + + var listString = new List(); + Assert.AreEqual(-1, listString.IndexOf("test")); + } + + [TestMethod] + public void List_Remove_FirstOccurrence() + { + var list = new List { 1, 2, 3, 2, 4 }; + Assert.IsTrue(list.Remove(2)); + Assert.AreEqual(4, list.Count); + Assert.AreEqual(3, list[1]); // Second 2 moved to position 1 + Assert.AreEqual(2, list[2]); // But one 2 still remains at position 2 + } + + [TestMethod] + public void List_Remove_FromEmptyList() + { + var list = new List(); + Assert.IsFalse(list.Remove(1)); + } + + [TestMethod] + public void List_ToArray_EmptyList() + { + var list = new List(); + var array = list.ToArray(); + Assert.IsNotNull(array); + Assert.AreEqual(0, array.Length); + + var listString = new List(); + var arrayString = listString.ToArray(); + Assert.IsNotNull(arrayString); + Assert.AreEqual(0, arrayString.Length); + } + + [TestMethod] + public void List_ToArray_Modification() + { + var list = new List { 1, 2, 3 }; + var array = list.ToArray(); + + // Modifying the array should not affect the list + array[0] = 100; + Assert.AreEqual(1, list[0]); + + // Modifying the list should not affect the array + list[0] = 200; + Assert.AreEqual(100, array[0]); + } + + [TestMethod] + public void List_Capacity_GetSet() + { + var list = new List(10); + Assert.IsTrue(list.Capacity >= 10); + + list.Add(1); + list.Add(2); + + // Set capacity to larger value + list.Capacity = 20; + Assert.AreEqual(20, list.Capacity); + Assert.AreEqual(2, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + } + + [TestMethod] + public void List_Capacity_SetLessThanCount_ThrowsException() + { + var list = new List { 1, 2, 3, 4, 5 }; + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + list.Capacity = 3; + }); + } + + [TestMethod] + public void List_Enumerator_Basic() + { + var list = new List { 1, 2, 3 }; + int sum = 0; + int count = 0; + + foreach (int item in list) + { + sum += item; + count++; + } + + Assert.AreEqual(6, sum); + Assert.AreEqual(3, count); + } + + [TestMethod] + public void List_Enumerator_EmptyList() + { + var list = new List(); + int count = 0; + + foreach (int item in list) + { + count++; + } + + Assert.AreEqual(0, count); + } + + [TestMethod] + public void List_Enumerator_ModificationDuringEnumeration_ThrowsException() + { + var list = new List { 1, 2, 3, 4, 5 }; + + Assert.ThrowsException(typeof(InvalidOperationException), () => + { + foreach (int item in list) + { + list.Add(10); // Modifying during enumeration + } + }); + } + + [TestMethod] + public void List_CopyTo_Array() + { + var list = new List { 1, 2, 3 }; + int[] array = new int[5]; + + list.CopyTo(array, 1); + + Assert.AreEqual(0, array[0]); + Assert.AreEqual(1, array[1]); + Assert.AreEqual(2, array[2]); + Assert.AreEqual(3, array[3]); + Assert.AreEqual(0, array[4]); + } + + [TestMethod] + public void List_CopyTo_WithIndex() + { + var list = new List { 1, 2, 3, 4, 5 }; + int[] array = new int[3]; + + list.CopyTo(1, array, 0, 3); + + Assert.AreEqual(2, array[0]); + Assert.AreEqual(3, array[1]); + Assert.AreEqual(4, array[2]); + } + + [TestMethod] + public void List_ForEach_Action() + { + var list = new List { 1, 2, 3 }; + int sum = 0; + + list.ForEach(item => sum += item); + + Assert.AreEqual(6, sum); + } + + [TestMethod] + public void List_ForEach_NullAction_ThrowsException() + { + var list = new List { 1, 2, 3 }; + + Assert.ThrowsException(typeof(ArgumentNullException), () => + { + list.ForEach(null); + }); + } + + [TestMethod] + public void List_ForEach_ModificationDuringForEach_ThrowsException() + { + var list = new List { 1, 2, 3 }; + + Assert.ThrowsException(typeof(InvalidOperationException), () => + { + list.ForEach(item => list.Add(10)); + }); + } + + [TestMethod] + public void List_MultipleOperations_Sequence() + { + var list = new List(); + + // Add items + for (int i = 0; i < 10; i++) + { + list.Add(i); + } + Assert.AreEqual(10, list.Count); + + // Insert in middle + list.Insert(5, 100); + Assert.AreEqual(11, list.Count); + Assert.AreEqual(100, list[5]); + + // Remove specific item + list.Remove(100); + Assert.AreEqual(10, list.Count); + + // RemoveAt + list.RemoveAt(0); + Assert.AreEqual(9, list.Count); + Assert.AreEqual(1, list[0]); + + // Clear + list.Clear(); + Assert.AreEqual(0, list.Count); + } + + [TestMethod] + public void List_WithReferenceTypes_Nulls() + { + var list = new List(); + list.Add(null); + list.Add("test"); + list.Add(null); + + Assert.AreEqual(3, list.Count); + Assert.IsNull(list[0]); + Assert.AreEqual("test", list[1]); + Assert.IsNull(list[2]); + + Assert.IsTrue(list.Contains(null)); + Assert.AreEqual(0, list.IndexOf(null)); + + list.Remove(null); + Assert.AreEqual(2, list.Count); + Assert.AreEqual("test", list[0]); + } + + [TestMethod] + public void List_IList_Interface() + { + IList list = new List { 1, 2, 3 }; + + Assert.AreEqual(3, list.Count); + Assert.IsFalse(list.IsFixedSize); + Assert.IsFalse(list.IsReadOnly); + Assert.IsFalse(list.IsSynchronized); + + Assert.AreEqual(1, list[0]); + list[0] = 10; + Assert.AreEqual(10, list[0]); + + list.Add(4); + Assert.AreEqual(4, list.Count); + + list.Remove(10); + Assert.AreEqual(3, list.Count); + + Assert.IsTrue(list.Contains(2)); + Assert.AreEqual(0, list.IndexOf(2)); + + list.Insert(0, 100); + Assert.AreEqual(100, list[0]); + + list.RemoveAt(0); + Assert.AreEqual(2, list[0]); + + list.Clear(); + Assert.AreEqual(0, list.Count); + } + + [TestMethod] + public void List_LargeCapacity_Growth() + { + var list = new List(); + + // Add many items to test capacity growth + for (int i = 0; i < 100; i++) + { + list.Add(i); + } + + Assert.AreEqual(100, list.Count); + + for (int i = 0; i < 100; i++) + { + Assert.AreEqual(i, list[i]); + } + } + } + + internal class DummyClass + { + public int Id { get; set; } + public string Name { get; set; } + + public DummyClass(int id, string name) + { + Id = id; + Name = name; + } + } +} diff --git a/Tests/GenericCollections/Properties/AssemblyInfo.cs b/Tests/GenericCollections/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a3735af --- /dev/null +++ b/Tests/GenericCollections/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright (c) 2021 nanoFramework contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/GenericCollections/packages.config b/Tests/GenericCollections/packages.config new file mode 100644 index 0000000..127ad74 --- /dev/null +++ b/Tests/GenericCollections/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Tests/GenericCollections/packages.lock.json b/Tests/GenericCollections/packages.lock.json new file mode 100644 index 0000000..c5413fb --- /dev/null +++ b/Tests/GenericCollections/packages.lock.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "dependencies": { + ".NETnanoFramework,Version=v1.0": { + "nanoFramework.CoreLibrary": { + "type": "Direct", + "requested": "[2.0.0-preview.20, 2.0.0-preview.20]", + "resolved": "2.0.0-preview.20", + "contentHash": "yBjbnxlEOHj8wJf/7ZK+QIUTNW1HZHLwtUW5d6g2vTGgjKu/xrSfvWQHPTCuE1MmXP+WPPT/aJy9k8HuIJmUmA==" + }, + "nanoFramework.TestFramework": { + "type": "Direct", + "requested": "[4.0.0-preview.44, 4.0.0-preview.44]", + "resolved": "4.0.0-preview.44", + "contentHash": "fnls8qrsOptatRi/26dDP/2BJBaBHmTrvENEAkUXYuuYMD0hGEk2D3gkRVXd2VItsBK3zkif9bcnCyPL+4lgJg==" + } + } + } +} \ No newline at end of file diff --git a/Tests/HashtableTests/HashtableTests.nfproj b/Tests/HashtableTests/HashtableTests.nfproj index 607066d..ca3db9e 100644 --- a/Tests/HashtableTests/HashtableTests.nfproj +++ b/Tests/HashtableTests/HashtableTests.nfproj @@ -33,14 +33,14 @@ - - ..\..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll + + ..\..\packages\nanoFramework.CoreLibrary.2.0.0-preview.20\lib\netnano1.0\mscorlib.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.TestFramework.dll + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.TestFramework.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.UnitTestLauncher.exe + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.UnitTestLauncher.exe @@ -53,4 +53,4 @@ - \ No newline at end of file + diff --git a/Tests/HashtableTests/packages.config b/Tests/HashtableTests/packages.config index 28d330b..127ad74 100644 --- a/Tests/HashtableTests/packages.config +++ b/Tests/HashtableTests/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/HashtableTests/packages.lock.json b/Tests/HashtableTests/packages.lock.json index 2137220..c5413fb 100644 --- a/Tests/HashtableTests/packages.lock.json +++ b/Tests/HashtableTests/packages.lock.json @@ -4,15 +4,15 @@ ".NETnanoFramework,Version=v1.0": { "nanoFramework.CoreLibrary": { "type": "Direct", - "requested": "[1.17.11, 1.17.11]", - "resolved": "1.17.11", - "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" + "requested": "[2.0.0-preview.20, 2.0.0-preview.20]", + "resolved": "2.0.0-preview.20", + "contentHash": "yBjbnxlEOHj8wJf/7ZK+QIUTNW1HZHLwtUW5d6g2vTGgjKu/xrSfvWQHPTCuE1MmXP+WPPT/aJy9k8HuIJmUmA==" }, "nanoFramework.TestFramework": { "type": "Direct", - "requested": "[3.0.77, 3.0.77]", - "resolved": "3.0.77", - "contentHash": "Py5W1oN84KMBmOOHCzdz6pyi3bZTnQu9BoqIx0KGqkhG3V8kGoem/t+BuCM0pMIWAyl2iMP1n2S9624YXmBJZw==" + "requested": "[4.0.0-preview.44, 4.0.0-preview.44]", + "resolved": "4.0.0-preview.44", + "contentHash": "fnls8qrsOptatRi/26dDP/2BJBaBHmTrvENEAkUXYuuYMD0hGEk2D3gkRVXd2VItsBK3zkif9bcnCyPL+4lgJg==" } } } diff --git a/Tests/QueueTests/QueueTests.nfproj b/Tests/QueueTests/QueueTests.nfproj index bf2048e..6315ccf 100644 --- a/Tests/QueueTests/QueueTests.nfproj +++ b/Tests/QueueTests/QueueTests.nfproj @@ -31,14 +31,14 @@ - - ..\..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll + + ..\..\packages\nanoFramework.CoreLibrary.2.0.0-preview.20\lib\netnano1.0\mscorlib.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.TestFramework.dll + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.TestFramework.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.UnitTestLauncher.exe + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.UnitTestLauncher.exe @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/Tests/QueueTests/packages.config b/Tests/QueueTests/packages.config index 28d330b..127ad74 100644 --- a/Tests/QueueTests/packages.config +++ b/Tests/QueueTests/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/QueueTests/packages.lock.json b/Tests/QueueTests/packages.lock.json index 2137220..c5413fb 100644 --- a/Tests/QueueTests/packages.lock.json +++ b/Tests/QueueTests/packages.lock.json @@ -4,15 +4,15 @@ ".NETnanoFramework,Version=v1.0": { "nanoFramework.CoreLibrary": { "type": "Direct", - "requested": "[1.17.11, 1.17.11]", - "resolved": "1.17.11", - "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" + "requested": "[2.0.0-preview.20, 2.0.0-preview.20]", + "resolved": "2.0.0-preview.20", + "contentHash": "yBjbnxlEOHj8wJf/7ZK+QIUTNW1HZHLwtUW5d6g2vTGgjKu/xrSfvWQHPTCuE1MmXP+WPPT/aJy9k8HuIJmUmA==" }, "nanoFramework.TestFramework": { "type": "Direct", - "requested": "[3.0.77, 3.0.77]", - "resolved": "3.0.77", - "contentHash": "Py5W1oN84KMBmOOHCzdz6pyi3bZTnQu9BoqIx0KGqkhG3V8kGoem/t+BuCM0pMIWAyl2iMP1n2S9624YXmBJZw==" + "requested": "[4.0.0-preview.44, 4.0.0-preview.44]", + "resolved": "4.0.0-preview.44", + "contentHash": "fnls8qrsOptatRi/26dDP/2BJBaBHmTrvENEAkUXYuuYMD0hGEk2D3gkRVXd2VItsBK3zkif9bcnCyPL+4lgJg==" } } } diff --git a/Tests/StackTests/StackTests.nfproj b/Tests/StackTests/StackTests.nfproj index 9e93c96..d9e5b42 100644 --- a/Tests/StackTests/StackTests.nfproj +++ b/Tests/StackTests/StackTests.nfproj @@ -33,14 +33,14 @@ - - ..\..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll + + ..\..\packages\nanoFramework.CoreLibrary.2.0.0-preview.20\lib\netnano1.0\mscorlib.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.TestFramework.dll + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.TestFramework.dll - - ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.UnitTestLauncher.exe + + ..\..\packages\nanoFramework.TestFramework.4.0.0-preview.44\lib\nanoFramework.UnitTestLauncher.exe @@ -56,4 +56,4 @@ - \ No newline at end of file + diff --git a/Tests/StackTests/packages.config b/Tests/StackTests/packages.config index 28d330b..127ad74 100644 --- a/Tests/StackTests/packages.config +++ b/Tests/StackTests/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/StackTests/packages.lock.json b/Tests/StackTests/packages.lock.json index 2137220..c5413fb 100644 --- a/Tests/StackTests/packages.lock.json +++ b/Tests/StackTests/packages.lock.json @@ -4,15 +4,15 @@ ".NETnanoFramework,Version=v1.0": { "nanoFramework.CoreLibrary": { "type": "Direct", - "requested": "[1.17.11, 1.17.11]", - "resolved": "1.17.11", - "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" + "requested": "[2.0.0-preview.20, 2.0.0-preview.20]", + "resolved": "2.0.0-preview.20", + "contentHash": "yBjbnxlEOHj8wJf/7ZK+QIUTNW1HZHLwtUW5d6g2vTGgjKu/xrSfvWQHPTCuE1MmXP+WPPT/aJy9k8HuIJmUmA==" }, "nanoFramework.TestFramework": { "type": "Direct", - "requested": "[3.0.77, 3.0.77]", - "resolved": "3.0.77", - "contentHash": "Py5W1oN84KMBmOOHCzdz6pyi3bZTnQu9BoqIx0KGqkhG3V8kGoem/t+BuCM0pMIWAyl2iMP1n2S9624YXmBJZw==" + "requested": "[4.0.0-preview.44, 4.0.0-preview.44]", + "resolved": "4.0.0-preview.44", + "contentHash": "fnls8qrsOptatRi/26dDP/2BJBaBHmTrvENEAkUXYuuYMD0hGEk2D3gkRVXd2VItsBK3zkif9bcnCyPL+4lgJg==" } } } diff --git a/nanoFramework.System.Collections.nuspec b/nanoFramework.System.Collections.nuspec index 81aa6a2..82f37ab 100644 --- a/nanoFramework.System.Collections.nuspec +++ b/nanoFramework.System.Collections.nuspec @@ -19,7 +19,7 @@ This package requires a target with System.Collections v$nativeVersion$ (checksum $checksum$). nanoFramework C# csharp netmf netnf - + @@ -33,4 +33,4 @@ This package requires a target with System.Collections v$nativeVersion$ (checksu - \ No newline at end of file + diff --git a/nanoFramework.System.Collections.sln b/nanoFramework.System.Collections.sln index d69855b..06ad82e 100644 --- a/nanoFramework.System.Collections.sln +++ b/nanoFramework.System.Collections.sln @@ -20,6 +20,8 @@ Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "QueueTests", "Tests\QueueTe EndProject Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "StackTests", "Tests\StackTests\StackTests.nfproj", "{988826B2-A0BB-481D-A357-078B6A0DD0D9}" EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "GenericCollections", "Tests\GenericCollections\GenericCollections.nfproj", "{2B275F71-E555-46BF-B1FE-C33D4CB06545}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +52,12 @@ Global {988826B2-A0BB-481D-A357-078B6A0DD0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {988826B2-A0BB-481D-A357-078B6A0DD0D9}.Release|Any CPU.Build.0 = Release|Any CPU {988826B2-A0BB-481D-A357-078B6A0DD0D9}.Release|Any CPU.Deploy.0 = Release|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Release|Any CPU.Build.0 = Release|Any CPU + {2B275F71-E555-46BF-B1FE-C33D4CB06545}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -58,6 +66,7 @@ Global {0F95CFBD-AF48-4645-93B1-0305D6D01F60} = {19416EDD-5F30-4B79-B69F-E5DB8FEC4F60} {B63451E5-198D-49FC-95D5-FE0307D8542A} = {19416EDD-5F30-4B79-B69F-E5DB8FEC4F60} {988826B2-A0BB-481D-A357-078B6A0DD0D9} = {19416EDD-5F30-4B79-B69F-E5DB8FEC4F60} + {2B275F71-E555-46BF-B1FE-C33D4CB06545} = {19416EDD-5F30-4B79-B69F-E5DB8FEC4F60} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {20AE6EDF-F6B9-47AD-916F-338A69E03CCA} diff --git a/nanoFramework.System.Collections/Collections/Generic/ICollection.cs b/nanoFramework.System.Collections/Collections/Generic/ICollection.cs new file mode 100644 index 0000000..ea91aea --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/ICollection.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + /// + /// Defines methods to manipulate generic collections. + /// + /// The type of the elements in the collection. + public interface ICollection : IEnumerable + { + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + int Count + { + get; + } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// if the is read-only; otherwise, . + /// + bool IsReadOnly + { + get; + } + + /// + /// Adds an item to the . + /// + /// The object to add to the . + void Add(T item); + + /// + /// Removes all items from the . + /// + void Clear(); + + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . + /// if is found in the ; otherwise, . + bool Contains(T item); + + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + void CopyTo(T[] array, int arrayIndex); + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . + /// true if item was successfully removed from the ; otherwise, false. This method also returns false if item is not found in the original . + bool Remove(T item); + } +} diff --git a/nanoFramework.System.Collections/Collections/Generic/ICollectionDebugView.cs b/nanoFramework.System.Collections/Collections/Generic/ICollectionDebugView.cs new file mode 100644 index 0000000..d6a888f --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/ICollectionDebugView.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Collections.Generic +{ + internal sealed class ICollectionDebugView + { + private readonly ICollection _collection; + + public ICollectionDebugView(ICollection collection) + { + ArgumentNullException.ThrowIfNull(collection); + + _collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get + { + T[] items = new T[_collection.Count]; + _collection.CopyTo(items, 0); + return items; + } + } + } +} diff --git a/nanoFramework.System.Collections/Collections/Generic/IList.cs b/nanoFramework.System.Collections/Collections/Generic/IList.cs new file mode 100644 index 0000000..beea2bb --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/IList.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + // An IList is an ordered collection of objects. The exact ordering + // is up to the implementation of the list, ranging from a sorted + // order to insertion order. + + /// + /// Represents a collection of objects that can be individually accessed by index. + /// + /// The type of elements in the list. + public interface IList : ICollection + { + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. + T this[int index] + { + get; + set; + } + + /// + /// Determines the index of a specific item in the . + /// + /// The object to locate in the . + /// The index of if found in the list; otherwise, -1. + int IndexOf(T item); + + /// + /// Inserts an item to the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the . + void Insert(int index, T item); + + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + void RemoveAt(int index); + } +} diff --git a/nanoFramework.System.Collections/Collections/Generic/IReadOnlyCollection.cs b/nanoFramework.System.Collections/Collections/Generic/IReadOnlyCollection.cs new file mode 100644 index 0000000..0524751 --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/IReadOnlyCollection.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + /// + /// Represents a strongly-typed, read-only collection of elements. + /// + /// + /// The type of the elements. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. + /// + public interface IReadOnlyCollection : IEnumerable + { + /// + /// Gets the number of elements in the collection. + /// + int Count + { + get; + } + } +} diff --git a/nanoFramework.System.Collections/Collections/Generic/IReadOnlyList.cs b/nanoFramework.System.Collections/Collections/Generic/IReadOnlyList.cs new file mode 100644 index 0000000..1c24753 --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/IReadOnlyList.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + /// + /// Represents a read-only collection of elements that can be accessed by index. + /// + /// The type of elements in the read-only list. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. + public interface IReadOnlyList : IReadOnlyCollection + { + /// + /// Gets the element at the specified index in the read-only list. + /// + /// The zero-based index of the element to get. + /// The element at the specified index in the read-only list. + T this[int index] + { + get; + } + } +} diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs new file mode 100644 index 0000000..e0f56ac --- /dev/null +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -0,0 +1,776 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Diagnostics; + +namespace System.Collections.Generic +{ + /// + /// Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists. + /// + /// The type of elements in the list. + [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public class List : IList, IList, IReadOnlyList + { + private const int DefaultCapacity = 4; + + internal T[] _items; + internal int _size; + internal int _version; + +#pragma warning disable CA1825, IDE0300 // avoid the extra generic instantiation for Array.Empty() + private static readonly T[] s_emptyArray = new T[0]; +#pragma warning restore CA1825, IDE0300 + + /// + /// Initializes a new instance of the class that is empty and has the default initial capacity. + /// + public List() + { + _items = s_emptyArray; + } + + /// + /// Initializes a new instance of the class that is empty and has the specified initial capacity. + /// + /// The number of elements that the new list can initially store. + /// is less than 0. + public List(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(); + } + + if (capacity == 0) + { + _items = s_emptyArray; + } + else + { + _items = new T[capacity]; + } + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied. + /// + /// The collection whose elements are copied to the new list. + /// is . + public List(IEnumerable collection) + { + if (collection == null) + { + throw new ArgumentNullException(); + } + + if (collection is ICollection c) + { + int count = c.Count; + + if (count == 0) + { + _items = s_emptyArray; + } + else + { + _items = new T[count]; + c.CopyTo(_items, 0); + _size = count; + } + } + else + { + _items = s_emptyArray; + + using IEnumerator en = collection.GetEnumerator(); + while (en.MoveNext()) + { + Add(en.Current); + } + } + } + + /// + /// Gets or sets the total number of elements the internal data structure can hold without resizing. + /// + /// + /// The number of elements that the can contain before resizing is required. + /// + public int Capacity + { + get => _items.Length; + + set + { + if (value < _size) + { + throw new ArgumentOutOfRangeException(); + } + + if (value != _items.Length) + { + if (value > 0) + { + T[] newItems = new T[value]; + + if (_size > 0) + { + Array.Copy(_items, newItems, _size); + } + + _items = newItems; + } + else + { + _items = s_emptyArray; + } + } + } + } + + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + public int Count => _size; + + /// + /// Gets a value indicating whether the has a fixed size. + /// + /// + /// if the has a fixed size; otherwise, . In the default implementation of , this property always returns false. + /// + bool IList.IsFixedSize => false; + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// if the is read-only; otherwise, . In the default implementation of , this property always returns false. + /// + bool ICollection.IsReadOnly => false; + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// if the is read-only; otherwise, . In the default implementation of , this property always returns . + /// + bool IList.IsReadOnly => false; + + /// + /// Gets a value indicating whether access to the is synchronized (thread safe). + /// + /// + /// if access to the is synchronized (thread safe); otherwise, . In the default implementation of , this property always returns . + /// + bool ICollection.IsSynchronized => false; + + /// + /// Gets an object that can be used to synchronize access to the . + /// + /// + /// An object that can be used to synchronize access to the . In the default implementation of , this property always returns the current instance. + /// + object ICollection.SyncRoot => this; + + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// + /// The element at the specified index. + /// + /// + /// + /// is less than 0. + /// + /// -or- + /// + /// is equal to or greater than . + /// + /// + public T this[int index] + { + get + { + // Following trick can reduce the range check by one + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(); + } + + return _items[index]; + } + + set + { + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(); + } + + _items[index] = value; + _version++; + } + } + + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// + /// The element at the specified index. + /// + /// is not a valid index in the . + /// The property is set and the value is of a type that isn't assignable to the IList. + object? IList.this[int index] + { + get => this[index]; + + set + { + try + { + this[index] = (T)value!; + } + catch (InvalidCastException) + { + throw new ArgumentException(); + } + } + } + + /// + /// Adds an object to the end of the . + /// + /// The object to be added to the end of the . The value can be for reference types. + public void Add(T item) + { + _version++; + + T[] array = _items; + int size = _size; + + if ((uint)size < (uint)array.Length) + { + _size = size + 1; + array[size] = item; + } + else + { + AddWithResize(item); + } + } + + private void AddWithResize(T item) + { + Debug.Assert(_size == _items.Length); + + int size = _size; + + Grow(size + 1); + + _size = size + 1; + _items[size] = item; + } + + /// + /// Adds an item to the . + /// + /// The object to add to the . The value can be for reference types. + /// + /// is of a type that is not assignable to the . + int IList.Add(object item) + { + try + { + Add((T)item!); + } + catch (InvalidCastException) + { + throw new ArgumentException(); + } + + return Count - 1; + } + + /// + /// Removes all elements from the . + /// + public void Clear() + { + _version++; + + // TODO + if (true) // RuntimeHelpers.IsReferenceOrContainsReferences()) + { + int size = _size; + _size = 0; + + if (size > 0) + { + Array.Clear(_items, 0, size); + } + } + else + { + _size = 0; + } + } + + /// + /// Determines whether an element is in the . + /// + /// The object to locate in the . The value can be for reference types. + /// if is found in the otherwise, . + public bool Contains(T item) + { + // PERF: IndexOf calls Array.IndexOf, which internally + // calls EqualityComparer.Default.IndexOf, which + // is specialized for different types. This + // boosts performance since instead of making a + // virtual method call each iteration of the loop, + // via EqualityComparer.Default.Equals, we + // only make one virtual call to EqualityComparer.IndexOf. + + return _size != 0 && IndexOf(item) >= 0; + } + + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . The value can be for reference types. + /// if is found in the otherwise, . + bool IList.Contains(object? item) + { + if (IsCompatibleObject(item)) + { + return Contains((T)item!); + } + + return false; + } + + /// + /// Copies the entire to a compatible one-dimensional array, starting at the beginning of the target array. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + public void CopyTo(T[] array) => CopyTo(array, 0); + + /// + /// Copies a range of elements from the to a compatible one-dimensional array, starting at the specified index of the target array. + /// + /// The zero-based index in the source at which copying begins. + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// The number of elements to copy. + /// + /// + /// index is equal to or greater than the Count of the source . + /// + /// -or- + /// + /// The number of elements in the source from index to the end of the is less than count. + /// + /// + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + if (_size - index < count) + { + throw new ArgumentException(); + } + + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, index, array, arrayIndex, count); + } + + /// + /// Copies the entire to a compatible one-dimensional array, starting at the specified index of the target array. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// is . + /// is less than 0. + /// The number of elements in the source is greater than the available space from to the end of the destination . + public void CopyTo(T[] array, int arrayIndex) + { + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, 0, array, arrayIndex, _size); + } + + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// The number of elements in the source is greater than the available space from to the end of the destination . + void ICollection.CopyTo(Array array, int arrayIndex) + { + try + { + // Array.Copy will check for NULL. + Array.Copy(_items, 0, array!, arrayIndex, _size); + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(); + } + } + + /// + /// Performs the specified action on each element of the until all elements have been processed or the action throws an exception. + /// + /// The delegate to perform on each element of the . + /// is . + /// The was modified during the operation. + public void ForEach(Action action) + { + if (action == null) + { + throw new ArgumentNullException(); + } + + int version = _version; + + for (int i = 0; i < _size; i++) + { + if (version != _version) + { + break; + } + + action(_items[i]); + } + + if (version != _version) + { + throw new InvalidOperationException(); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// A for the . + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Returns an enumerator that iterates through the . + /// + /// An for the . + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Returns an enumerator that iterates through the . + /// + /// An for the . + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + + /// + /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire . + /// + /// The object to locate in the . The value can be for reference types. + /// The zero-based index of the first occurrence of within the entire , if found; otherwise, -1. + public int IndexOf(T item) => Array.IndexOf(_items, item, 0, _size); + + /// + /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire . + /// + /// The object to locate in the . The value can be for reference types. + /// The zero-based index of the first occurrence of within the entire , if found; otherwise, -1. + int IList.IndexOf(object? item) + { + if (IsCompatibleObject(item)) + { + return IndexOf((T)item!); + } + return -1; + } + + /// + /// Inserts an element into the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert. The value can be for reference types. + /// is less than 0. + public void Insert(int index, T item) + { + // Note that insertions at the end are legal. + if ((uint)index > (uint)_size) + { + throw new ArgumentOutOfRangeException(); + } + + if (_size == _items.Length) + { + GrowForInsertion(index, 1); + } + else if (index < _size) + { + Array.Copy(_items, index, _items, index + 1, _size - index); + } + + _items[index] = item; + _size++; + _version++; + } + + /// + /// Inserts an item to the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the . The value can be for reference types. + /// is and is a value type. + /// is of a type that is not assignable to the . + void IList.Insert(int index, object? item) + { + if (!(default(T) == null) && item == null) + { + throw new ArgumentNullException(); + } + + try + { + Insert(index, (T)item!); + } + catch (InvalidCastException) + { + throw new ArgumentException(); + } + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . The value can be for reference types. + /// if was successfully removed from the ; otherwise, . This method also returns if is not found in the original . + public bool Remove(T item) + { + int index = IndexOf(item); + + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . The value can be for reference types. + void IList.Remove(object? item) + { + if (IsCompatibleObject(item)) + { + Remove((T)item!); + } + } + + /// + /// Removes the element at the specified index of the . + /// + /// The zero-based index of the element to remove. + /// is less than 0. + public void RemoveAt(int index) + { + if ((uint)index >= (uint)_size) + { + throw new ArgumentOutOfRangeException(); + } + + _size--; + + if (index < _size) + { + Array.Copy(_items, index + 1, _items, index, _size - index); + } + + // TODO + //if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + _items[_size] = default!; + } + + _version++; + } + + /// + /// Copies the elements of the to a new array. + /// + /// An array containing copies of the elements of the . + public T[] ToArray() + { + if (_size == 0) + { + return s_emptyArray; + } + + T[] array = new T[_size]; + Array.Copy(_items, array, _size); + + return array; + } + + internal void Grow(int capacity) + { + Capacity = GetNewCapacity(capacity); + } + + internal void GrowForInsertion(int indexToInsert, int insertionCount = 1) + { + Debug.Assert(insertionCount > 0); + + int requiredCapacity = checked(_size + insertionCount); + int newCapacity = GetNewCapacity(requiredCapacity); + + // Inline and adapt logic from set_Capacity + + T[] newItems = new T[newCapacity]; + if (indexToInsert != 0) + { + Array.Copy(_items, newItems, length: indexToInsert); + } + + if (_size != indexToInsert) + { + Array.Copy(_items, indexToInsert, newItems, indexToInsert + insertionCount, _size - indexToInsert); + } + + _items = newItems; + } + + private int GetNewCapacity(int capacity) + { + Debug.Assert(_items.Length < capacity); + + int newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length; + + // Allow the list to grow to maximum possible capacity (limited to the available memory) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newCapacity > int.MaxValue) + { + newCapacity = int.MaxValue; + } + + // If the computed capacity is still less than specified, set to the original argument. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. + if (newCapacity < capacity) + { + newCapacity = capacity; + } + + return newCapacity; + } + + private static bool IsCompatibleObject(object? value) + { + // Non-null values are fine. Only accept nulls if T is a class or Nullable. + // Note that default(T) is not equal to null for value types except when T is Nullable. + return (value is T) || (value == null && default(T) == null); + } + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator + { + private readonly List _list; + private readonly int _version; + + private int _index; + private T? _current; + + internal Enumerator(List list) + { + _list = list; + _version = list._version; + } + + /// + /// Releases all resources used by the . + /// + public void Dispose() + { + } + + /// + /// Advances the enumerator to the next element of the . + /// + /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + List localList = _list; + + if (_version != _list._version) + { + throw new InvalidOperationException(); + } + + if ((uint)_index < (uint)localList._size) + { + _current = localList._items[_index]; + _index++; + return true; + } + + _current = default; + _index = -1; + return false; + } + + /// + /// Gets the element at the current position of the enumerator. + /// + /// + /// The element in the at the current position of the enumerator. + /// + public T Current => _current!; + + /// + /// Gets the element at the current position of the enumerator. + /// + /// + /// The element in the at the current position of the enumerator. + /// + object? IEnumerator.Current + { + get + { + if (_index <= 0) + { + throw new InvalidOperationException(); + } + + return _current; + } + } + + /// + /// Sets the enumerator to its initial position, which is before the first element in the . + /// + /// The collection was modified after the enumerator was created. + void IEnumerator.Reset() + { + if (_version != _list._version) + { + throw new InvalidOperationException(); + } + + _index = 0; + _current = default; + } + } + } +} diff --git a/nanoFramework.System.Collections/Properties/AssemblyInfo.cs b/nanoFramework.System.Collections/Properties/AssemblyInfo.cs index 6ccbafe..bf56e00 100644 --- a/nanoFramework.System.Collections/Properties/AssemblyInfo.cs +++ b/nanoFramework.System.Collections/Properties/AssemblyInfo.cs @@ -16,6 +16,6 @@ //////////////////////////////////////////////////////////////// // update this whenever the native assembly signature changes // -[assembly: AssemblyNativeVersion("100.0.2.0")] +[assembly: AssemblyNativeVersion("100.0.3.0")] //////////////////////////////////////////////////////////////// diff --git a/nanoFramework.System.Collections/nanoFramework.System.Collections.nfproj b/nanoFramework.System.Collections/nanoFramework.System.Collections.nfproj index 54acc73..68bee6c 100644 --- a/nanoFramework.System.Collections/nanoFramework.System.Collections.nfproj +++ b/nanoFramework.System.Collections/nanoFramework.System.Collections.nfproj @@ -12,7 +12,8 @@ ADEC287F-9989-40D3-A560-D918B90FFB03 Library 512 - + default + System.Collections nanoFramework.System.Collections v1.0 True @@ -48,6 +49,12 @@ + + + + + + @@ -63,8 +70,8 @@ - - ..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll + + ..\packages\nanoFramework.CoreLibrary.2.0.0-preview.20\lib\netnano1.0\mscorlib.dll @@ -81,4 +88,4 @@ - \ No newline at end of file + diff --git a/nanoFramework.System.Collections/packages.config b/nanoFramework.System.Collections/packages.config index 82872d2..345172e 100644 --- a/nanoFramework.System.Collections/packages.config +++ b/nanoFramework.System.Collections/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/nanoFramework.System.Collections/packages.lock.json b/nanoFramework.System.Collections/packages.lock.json index a1f8d02..9242b3e 100644 --- a/nanoFramework.System.Collections/packages.lock.json +++ b/nanoFramework.System.Collections/packages.lock.json @@ -4,9 +4,9 @@ ".NETnanoFramework,Version=v1.0": { "nanoFramework.CoreLibrary": { "type": "Direct", - "requested": "[1.17.11, 1.17.11]", - "resolved": "1.17.11", - "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" + "requested": "[2.0.0-preview.20, 2.0.0-preview.20]", + "resolved": "2.0.0-preview.20", + "contentHash": "yBjbnxlEOHj8wJf/7ZK+QIUTNW1HZHLwtUW5d6g2vTGgjKu/xrSfvWQHPTCuE1MmXP+WPPT/aJy9k8HuIJmUmA==" }, "Nerdbank.GitVersioning": { "type": "Direct", From 03ce7d18357e4307122a6cb670e6b2dbe702435b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:00 +0000 Subject: [PATCH 02/11] Update nanoFramework.System.Collections/Collections/Generic/List.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nanoFramework.System.Collections/Collections/Generic/List.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index e0f56ac..f01dba9 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -286,7 +286,7 @@ private void AddWithResize(T item) /// Adds an item to the . /// /// The object to add to the . The value can be for reference types. - /// + /// The index at which the new element was inserted. /// is of a type that is not assignable to the . int IList.Add(object item) { From cdac3f252b010337eacf04dda2a2370db0c4e7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:16 +0000 Subject: [PATCH 03/11] Update nanoFramework.System.Collections/Collections/Generic/List.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nanoFramework.System.Collections/Collections/Generic/List.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index f01dba9..699f5bc 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -309,8 +309,8 @@ public void Clear() { _version++; - // TODO - if (true) // RuntimeHelpers.IsReferenceOrContainsReferences()) + // Only clear the array if T is a reference type + if (!typeof(T).IsValueType) { int size = _size; _size = 0; From 6f4fc651061ed5da0cfefbb9429ced244054bfdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:26 +0000 Subject: [PATCH 04/11] Update nanoFramework.System.Collections/Collections/Generic/List.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nanoFramework.System.Collections/Collections/Generic/List.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index 699f5bc..618f40f 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -459,7 +459,7 @@ public void ForEach(Action action) /// /// Returns an enumerator that iterates through the . /// - /// A for the . + /// A for the . public Enumerator GetEnumerator() => new Enumerator(this); /// From 907f7a3aaa2db0b2e1a0873dd0bcf8ef70f59a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:36 +0000 Subject: [PATCH 05/11] Update Tests/GenericCollections/ListTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Tests/GenericCollections/ListTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/GenericCollections/ListTests.cs b/Tests/GenericCollections/ListTests.cs index 8dd92dc..030f548 100644 --- a/Tests/GenericCollections/ListTests.cs +++ b/Tests/GenericCollections/ListTests.cs @@ -649,7 +649,7 @@ public void List_Enumerator_EmptyList() var list = new List(); int count = 0; - foreach (int item in list) + foreach (int _ in list) { count++; } From c21eaeba9b30f7d7bde3d97ff4fd681b0feb924c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:46 +0000 Subject: [PATCH 06/11] Update Tests/GenericCollections/ListTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Tests/GenericCollections/ListTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/GenericCollections/ListTests.cs b/Tests/GenericCollections/ListTests.cs index 030f548..e9e3c1c 100644 --- a/Tests/GenericCollections/ListTests.cs +++ b/Tests/GenericCollections/ListTests.cs @@ -368,7 +368,7 @@ public void List_Constructor_IEnumerable_Null_ThrowsException() { Assert.ThrowsException(typeof(ArgumentNullException), () => { - var list = new List(null); + new List(null); }); } From d1bbb1143012c7362f95c889f736c39a6e6a8d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:42:53 +0000 Subject: [PATCH 07/11] Update Tests/GenericCollections/ListTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Tests/GenericCollections/ListTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/GenericCollections/ListTests.cs b/Tests/GenericCollections/ListTests.cs index e9e3c1c..d8603c8 100644 --- a/Tests/GenericCollections/ListTests.cs +++ b/Tests/GenericCollections/ListTests.cs @@ -664,7 +664,7 @@ public void List_Enumerator_ModificationDuringEnumeration_ThrowsException() Assert.ThrowsException(typeof(InvalidOperationException), () => { - foreach (int item in list) + foreach (int _ in list) { list.Add(10); // Modifying during enumeration } From 41a8b81ada48acd9e13d218f87800dec7fe26d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:43:51 +0000 Subject: [PATCH 08/11] Update nanoFramework.System.Collections/Collections/Generic/List.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nanoFramework.System.Collections/Collections/Generic/List.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index 618f40f..9508ae7 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -532,7 +532,7 @@ public void Insert(int index, T item) /// is of a type that is not assignable to the . void IList.Insert(int index, object? item) { - if (!(default(T) == null) && item == null) + if (default(T) != null && item == null) { throw new ArgumentNullException(); } From b66b6bd13f51d5e34f5f45a2eeb53676691489a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 17:46:01 +0000 Subject: [PATCH 09/11] Revert "Update nanoFramework.System.Collections/Collections/Generic/List.cs" This reverts commit f591b882bc333687b2f0ba145c60e931a464e2b9. --- nanoFramework.System.Collections/Collections/Generic/List.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index 9508ae7..69baade 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -309,8 +309,8 @@ public void Clear() { _version++; - // Only clear the array if T is a reference type - if (!typeof(T).IsValueType) + // TODO + if (true) // RuntimeHelpers.IsReferenceOrContainsReferences()) { int size = _size; _size = 0; From 6135ffdf70a388ec0a293cedf1fa74990033d461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 18:18:23 +0000 Subject: [PATCH 10/11] Add back original code --- nanoFramework.System.Collections/Collections/Generic/List.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nanoFramework.System.Collections/Collections/Generic/List.cs b/nanoFramework.System.Collections/Collections/Generic/List.cs index 69baade..fafbf82 100644 --- a/nanoFramework.System.Collections/Collections/Generic/List.cs +++ b/nanoFramework.System.Collections/Collections/Generic/List.cs @@ -4,6 +4,7 @@ #nullable enable using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Collections.Generic { @@ -309,8 +310,7 @@ public void Clear() { _version++; - // TODO - if (true) // RuntimeHelpers.IsReferenceOrContainsReferences()) + if (RuntimeHelpers.IsReferenceOrContainsReferences()) { int size = _size; _size = 0; From 356bec80e21630f28ced4be3f36e4a5049e676b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 20 Nov 2025 20:57:23 +0000 Subject: [PATCH 11/11] Improve unit test for List.Clear() - Now using a struct including value types containing references. --- Tests/GenericCollections/ListTests.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Tests/GenericCollections/ListTests.cs b/Tests/GenericCollections/ListTests.cs index d8603c8..4f0d3f1 100644 --- a/Tests/GenericCollections/ListTests.cs +++ b/Tests/GenericCollections/ListTests.cs @@ -163,6 +163,18 @@ public void List_Clear() Assert.AreEqual(2, listClass.Count); listClass.Clear(); Assert.AreEqual(0, listClass.Count); + + // Test value type containing references + var listStruct = new List + { + new(1, "One"), + new(2, "Two"), + new(3, "Three") + }; + + Assert.AreEqual(3, listStruct.Count); + listStruct.Clear(); + Assert.AreEqual(0, listStruct.Count); } [TestMethod] @@ -848,4 +860,16 @@ public DummyClass(int id, string name) Name = name; } } + + internal struct DummyStruct + { + public int Id { get; set; } + public string Name { get; set; } + + public DummyStruct(int id, string name) + { + Id = id; + Name = name; + } + } }