From 4d3a1f046de0b55143d8811be20d6c091699fb0c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 05:24:35 +0000 Subject: [PATCH] fix: empty a set when ExceptWith is passed the set itself [patch] ContiguousSet, OrderedSet and InsertionOrderSet removed each item of `other` while enumerating it. When `other` was the set itself, the ContiguousSet enumerator skipped every other element and the List-backed sets threw InvalidOperationException. Clear the set instead, matching HashSet. Fixes ktsu-dev/Containers#65 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012Hhb8knddbu4LRSs2uu9jR --- Containers.Test/ContiguousSetTests.cs | 10 ++++++++++ Containers.Test/InsertionOrderSetTests.cs | 10 ++++++++++ Containers.Test/OrderedSetTests.cs | 10 ++++++++++ Containers/ContiguousSet.cs | 7 +++++++ Containers/InsertionOrderSet.cs | 7 +++++++ Containers/OrderedSet.cs | 7 +++++++ 6 files changed, 51 insertions(+) diff --git a/Containers.Test/ContiguousSetTests.cs b/Containers.Test/ContiguousSetTests.cs index 321ff2f..c7250fd 100644 --- a/Containers.Test/ContiguousSetTests.cs +++ b/Containers.Test/ContiguousSetTests.cs @@ -297,6 +297,16 @@ public void ExceptWith_RemovesCommonElements() Assert.DoesNotContain(4, set1); } + [TestMethod] + public void ExceptWith_Self_EmptiesSet() + { + ContiguousSet set = [.. new int[] { 1, 2, 3, 4 }]; + + set.ExceptWith(set); + + Assert.IsEmpty(set); + } + [TestMethod] public void SymmetricExceptWith_KeepsUniqueElements() { diff --git a/Containers.Test/InsertionOrderSetTests.cs b/Containers.Test/InsertionOrderSetTests.cs index 7e198bc..313ef5f 100644 --- a/Containers.Test/InsertionOrderSetTests.cs +++ b/Containers.Test/InsertionOrderSetTests.cs @@ -310,6 +310,16 @@ public void ExceptWith_RemovesCommonElements() Assert.AreEqual(3, set1.ElementAt(1)); } + [TestMethod] + public void ExceptWith_Self_EmptiesSet() + { + InsertionOrderSet set = [.. new int[] { 1, 2, 3, 4 }]; + + set.ExceptWith(set); + + Assert.IsEmpty(set); + } + [TestMethod] public void WorksWithStrings_MaintainsInsertionOrder() { diff --git a/Containers.Test/OrderedSetTests.cs b/Containers.Test/OrderedSetTests.cs index 0610cec..c49465d 100644 --- a/Containers.Test/OrderedSetTests.cs +++ b/Containers.Test/OrderedSetTests.cs @@ -373,6 +373,16 @@ public void ExceptWith_RemovesSpecifiedElements() Assert.AreSequenceEqual(expected, set1); } + [TestMethod] + public void ExceptWith_Self_EmptiesSet() + { + OrderedSet set = [.. new int[] { 1, 2, 3, 4 }]; + + set.ExceptWith(set); + + Assert.IsEmpty(set); + } + [TestMethod] public void ExceptWith_NullCollection_ThrowsArgumentNullException() { diff --git a/Containers/ContiguousSet.cs b/Containers/ContiguousSet.cs index bc60ff1..c6c56fb 100644 --- a/Containers/ContiguousSet.cs +++ b/Containers/ContiguousSet.cs @@ -371,6 +371,13 @@ public void ExceptWith(IEnumerable other) { Ensure.NotNull(other); + // Removing while enumerating this set would skip or throw; excepting a set from itself empties it + if (ReferenceEquals(other, this)) + { + Clear(); + return; + } + foreach (T item in other) { Remove(item); diff --git a/Containers/InsertionOrderSet.cs b/Containers/InsertionOrderSet.cs index 9b92842..85ec45c 100644 --- a/Containers/InsertionOrderSet.cs +++ b/Containers/InsertionOrderSet.cs @@ -300,6 +300,13 @@ public void ExceptWith(IEnumerable other) { Ensure.NotNull(other); + // Removing while enumerating this set would skip or throw; excepting a set from itself empties it + if (ReferenceEquals(other, this)) + { + Clear(); + return; + } + foreach (T item in other) { Remove(item); diff --git a/Containers/OrderedSet.cs b/Containers/OrderedSet.cs index dd584c9..62de21d 100644 --- a/Containers/OrderedSet.cs +++ b/Containers/OrderedSet.cs @@ -381,6 +381,13 @@ public void ExceptWith(IEnumerable other) { Ensure.NotNull(other); + // Removing while enumerating this set would skip or throw; excepting a set from itself empties it + if (ReferenceEquals(other, this)) + { + Clear(); + return; + } + foreach (T item in other) { Remove(item);