diff --git a/src/BizHawk.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
index edc01b9534..a883a0f631 100644
--- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs
+++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs
@@ -105,6 +105,21 @@ namespace BizHawk.Common.CollectionExtensions
throw new InvalidOperationException("Item not found");
}
+ ///
+ ///
+ /// (This is an extension method which reimplements for other collections.
+ /// It defers to the existing AddRange if the receiver's type is or a subclass.)
+ ///
+ public static void AddRange(this ICollection list, IEnumerable collection)
+ {
+ if (list is List listImpl)
+ {
+ listImpl.AddRange(collection);
+ return;
+ }
+ foreach (var item in collection) list.Add(item);
+ }
+
public static T? FirstOrNull(this IEnumerable list, Func predicate)
where T : struct
{
@@ -113,5 +128,32 @@ namespace BizHawk.Common.CollectionExtensions
return t;
return null;
}
+
+ ///
+ ///
+ /// (This is an extension method which reimplements for other collections.
+ /// It defers to the existing RemoveAll if the receiver's type is or a subclass.)
+ ///
+ public static int RemoveAll(this ICollection list, Predicate match)
+ {
+ if (list is List listImpl) return listImpl.RemoveAll(match);
+ var c = list.Count;
+ if (list is IList iList)
+ {
+ for (var i = 0; i < iList.Count; i++)
+ {
+ if (match(iList[i])) iList.RemoveAt(i--);
+ }
+ }
+ else
+ {
+ foreach (var item in list.Where(item => match(item)) // can't simply cast to Func
+ .ToList()) // very important
+ {
+ list.Remove(item);
+ }
+ }
+ return c - list.Count;
+ }
}
}
diff --git a/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs b/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs
new file mode 100644
index 0000000000..d545ab2e89
--- /dev/null
+++ b/src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using BizHawk.Common.CollectionExtensions;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using CE = BizHawk.Common.CollectionExtensions.CollectionExtensions;
+
+namespace BizHawk.Tests.Common.CollectionExtensions
+{
+ [TestClass]
+ public class CollectionExtensionTests
+ {
+ /// there isn't really an implementor which isn't and isn't immutable... so I made one
+ private readonly struct ListImpl : IList
+ {
+ private readonly IList _wrapped;
+
+ public int Count => _wrapped.Count;
+
+ public bool IsReadOnly => false;
+
+ public ListImpl(params int[] init) => _wrapped = new List(init);
+
+ public readonly int this[int index]
+ {
+ get => _wrapped[index];
+ set => throw new NotImplementedException();
+ }
+
+ public readonly void Add(int item) => throw new NotImplementedException();
+
+ public readonly void Clear() => throw new NotImplementedException();
+
+ public readonly bool Contains(int item) => throw new NotImplementedException();
+
+ public readonly void CopyTo(int[] array, int arrayIndex) => throw new NotImplementedException();
+
+ public readonly IEnumerator GetEnumerator() => throw new NotImplementedException();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public readonly int IndexOf(int item) => throw new NotImplementedException();
+
+ public readonly void Insert(int index, int item) => throw new NotImplementedException();
+
+ public readonly bool Remove(int item) => throw new NotImplementedException();
+
+ public readonly void RemoveAt(int index) => _wrapped.RemoveAt(index);
+ }
+
+ [TestMethod]
+ public void TestAddRange()
+ {
+ List a = new(new[] { 1, 2 });
+ CE.AddRange(a, new[] { 3, 4 });
+ Assert.AreEqual(4, a.Count, nameof(CE.AddRange) + " failed on List");
+
+ SortedSet b = new(new[] { 1, 2 });
+ b.AddRange(new[] { 3, 4 });
+ Assert.AreEqual(4, b.Count, nameof(CE.AddRange) + " failed on (ICollection not List)");
+ }
+
+ [TestMethod]
+ public void TestRemoveAll()
+ {
+ static bool Predicate(int i) => 2 <= i && i <= 3;
+
+ List a = new(new[] { 1, 2, 3, 4 });
+ CE.RemoveAll(a, Predicate);
+ Assert.AreEqual(2, a.Count, nameof(CE.RemoveAll) + " failed on List");
+
+ ListImpl b = new(1, 2, 3, 4);
+ b.RemoveAll(Predicate);
+ Assert.AreEqual(2, b.Count, nameof(CE.RemoveAll) + " failed on (IList not List)");
+
+ SortedSet c = new(new[] { 1, 2, 3, 4 });
+ c.RemoveAll(Predicate);
+ Assert.AreEqual(2, c.Count, nameof(CE.RemoveAll) + " failed on (ICollection not IList)");
+ }
+ }
+}