diff --git a/GameFrameX.Foundation.Extensions/IEnumerableExtensions.cs b/GameFrameX.Foundation.Extensions/IEnumerableExtensions.cs index 2ed74a1..6736111 100644 --- a/GameFrameX.Foundation.Extensions/IEnumerableExtensions.cs +++ b/GameFrameX.Foundation.Extensions/IEnumerableExtensions.cs @@ -1283,26 +1283,65 @@ public static bool SequenceEqual(this IEnumerable first, IEnumerable if (first is ICollection source1 && second is ICollection source2) { - if (source1.Count != source2.Count) - { - return false; - } + return EqualAsCollection(source1, source2, condition); + } - if (source1 is IList list1 && source2 is IList list2) + return EqualByEnumerator(first, second, condition); + } + + /// + /// 当双方都已实现 时走集合快路径:长度不一致直接 false;只要一方同时实现 就以 索引方式逐元素比较,否则回退到枚举器比较。 + /// + /// + /// Compares two with a custom condition. + /// Returns false immediately when the counts differ; + /// if both sides also implement the indexed access path is used, + /// otherwise the enumerator fallback is used. + /// Splitting this branch out of keeps that method's cognitive complexity within Sonar S3776's 15. + /// + /// 第一个序列 / The first sequence + /// 第二个序列 / The second sequence + /// 用于比较两个元素的条件 / The condition to compare two elements + /// 长度相等且所有对应元素满足条件时返回 true / Returns true if lengths match and all pairs satisfy + private static bool EqualAsCollection(ICollection first, ICollection second, Func condition) + { + if (first.Count != second.Count) + { + return false; + } + + if (first is IList list1 && second is IList list2) + { + var count = list1.Count; + for (var index = 0; index < count; ++index) { - var count = source1.Count; - for (var index = 0; index < count; ++index) + if (!condition(list1[index], list2[index])) { - if (!condition(list1[index], list2[index])) - { - return false; - } + return false; } - - return true; } + + return true; } + return EqualByEnumerator(first, second, condition); + } + + /// + /// 通过并行枚举两个 序列并以 逐元素比较,确认二者长度相等且每个对应位置的元素都满足条件。 + /// + /// + /// Compares two sequences element-by-element via parallel enumeration. + /// Used by when no fast path is available, + /// and by the collection fast path when at least one side is not an . + /// Splitting this branch out of keeps that method's cognitive complexity within Sonar S3776's 15. + /// + /// 第一个序列 / The first sequence + /// 第二个序列 / The second sequence + /// 用于比较两个元素的条件 / The condition to compare two elements + /// 长度相等且所有对应元素满足条件时返回 true / Returns true if lengths match and all pairs satisfy + private static bool EqualByEnumerator(IEnumerable first, IEnumerable second, Func condition) + { using var enumerator1 = first.GetEnumerator(); using var enumerator2 = second.GetEnumerator(); while (enumerator1.MoveNext()) diff --git a/GameFrameX.Foundation.Tests/Extensions/IEnumerableExtensionsTests.cs b/GameFrameX.Foundation.Tests/Extensions/IEnumerableExtensionsTests.cs index 5af1fb2..6bff2cc 100644 --- a/GameFrameX.Foundation.Tests/Extensions/IEnumerableExtensionsTests.cs +++ b/GameFrameX.Foundation.Tests/Extensions/IEnumerableExtensionsTests.cs @@ -361,6 +361,68 @@ public void SequenceEqual_DifferentTypes_ShouldReturnTrue() Assert.True(result); } + + [Fact] + public void SequenceEqual_ICollectionLengthMismatch_ShouldReturnFalse() + { + // Arrange: both sides are IList (also ICollection) with differing lengths. + var first = new[] { 1, 2, 3 }; + var second = new[] { 1, 2 }; + + // Act + var result = first.SequenceEqual(second, (x, y) => x == y); + + // Assert + Assert.False(result); + } + + [Fact] + public void SequenceEqual_PredicateFailure_ShouldReturnFalse() + { + // Arrange: identical lengths but predicate rejects the second element. + var first = new[] { 1, 2, 3 }; + var second = new[] { 1, 9, 3 }; + + // Act + var result = first.SequenceEqual(second, (x, y) => x == y); + + // Assert + Assert.False(result); + } + + [Fact] + public void SequenceEqual_CollectionNotList_ShouldReturnTrue() + { + // Arrange: LinkedList implements ICollection but not IList, so the + // helper must fall back to the enumerator path while still returning true. + var first = new LinkedList(new[] { 1, 2, 3 }); + var second = new LinkedList(new[] { 1, 2, 3 }); + + // Act + var result = first.SequenceEqual(second, (x, y) => x == y); + + // Assert + Assert.True(result); + } + + [Fact] + public void SequenceEqual_LazyEnumerable_ShouldReturnTrue() + { + // Arrange: pure IEnumerable with no collection optimisation available. + static IEnumerable Range(int from, int count) + { + for (var i = 0; i < count; i++) + { + yield return from + i; + } + } + + // Act + var result = Range(10, 3).SequenceEqual(Range(10, 3), (x, y) => x == y); + + // Assert + Assert.True(result); + } #endregion #region CompareChanges Tests