From 3860199c525f1cb14b0c814774387a663444a7cc Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Fri, 26 Mar 2021 18:41:24 +1000 Subject: [PATCH] Add ext. methods for calling List methods on collection interfaces --- .../Extensions/CollectionExtensions.cs | 42 ++++++++++ .../CollectionExtensionTests.cs | 84 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/BizHawk.Tests/Common/CollectionExtensions/CollectionExtensionTests.cs 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)"); + } + } +}