Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions GameFrameX.Foundation.Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,26 +1283,65 @@ public static bool SequenceEqual<T1, T2>(this IEnumerable<T1> first, IEnumerable

if (first is ICollection<T1> source1 && second is ICollection<T2> source2)
{
if (source1.Count != source2.Count)
{
return false;
}
return EqualAsCollection(source1, source2, condition);
}

if (source1 is IList<T1> list1 && source2 is IList<T2> list2)
return EqualByEnumerator(first, second, condition);
}

/// <summary>
/// 当双方都已实现 <see cref="ICollection{T}"/> 时走集合快路径:长度不一致直接 <c>false</c>;只要一方同时实现 <see cref="IList{T}"/> 就以 <see cref="IList{T}"/> 索引方式逐元素比较,否则回退到枚举器比较。
/// </summary>
/// <remarks>
/// Compares two <see cref="ICollection{T}"/> with a custom condition.
/// Returns <c>false</c> immediately when the counts differ;
/// if both sides also implement <see cref="IList{T}"/> the indexed access path is used,
/// otherwise the enumerator fallback is used.
/// Splitting this branch out of <see cref="SequenceEqual{T1, T2}"/> keeps that method's cognitive complexity within Sonar S3776's 15.
/// </remarks>
/// <param name="first">第一个序列 / The first sequence</param>
/// <param name="second">第二个序列 / The second sequence</param>
/// <param name="condition">用于比较两个元素的条件 / The condition to compare two elements</param>
/// <returns>长度相等且所有对应元素满足条件时返回 <c>true</c> / Returns <c>true</c> if lengths match and all pairs satisfy <paramref name="condition"/></returns>
private static bool EqualAsCollection<T1, T2>(ICollection<T1> first, ICollection<T2> second, Func<T1, T2, bool> condition)
{
if (first.Count != second.Count)
{
return false;
}

if (first is IList<T1> list1 && second is IList<T2> 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);
}

/// <summary>
/// 通过并行枚举两个 <see cref="IEnumerable{T}"/> 序列并以 <paramref name="condition"/> 逐元素比较,确认二者长度相等且每个对应位置的元素都满足条件。
/// </summary>
/// <remarks>
/// Compares two sequences element-by-element via parallel enumeration.
/// Used by <see cref="SequenceEqual{T1, T2}"/> when no <see cref="ICollection{T}"/> fast path is available,
/// and by the collection fast path when at least one side is not an <see cref="IList{T}"/>.
/// Splitting this branch out of <see cref="SequenceEqual{T1, T2}"/> keeps that method's cognitive complexity within Sonar S3776's 15.
/// </remarks>
/// <param name="first">第一个序列 / The first sequence</param>
/// <param name="second">第二个序列 / The second sequence</param>
/// <param name="condition">用于比较两个元素的条件 / The condition to compare two elements</param>
/// <returns>长度相等且所有对应元素满足条件时返回 <c>true</c> / Returns <c>true</c> if lengths match and all pairs satisfy <paramref name="condition"/></returns>
private static bool EqualByEnumerator<T1, T2>(IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, bool> condition)
{
using var enumerator1 = first.GetEnumerator();
using var enumerator2 = second.GetEnumerator();
while (enumerator1.MoveNext())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,68 @@ public void SequenceEqual_DifferentTypes_ShouldReturnTrue()
Assert.True(result);
}


[Fact]
public void SequenceEqual_ICollectionLengthMismatch_ShouldReturnFalse()
{
// Arrange: both sides are IList<T> (also ICollection<T>) 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<T> implements ICollection<T> but not IList<T>, so the
// helper must fall back to the enumerator path while still returning true.
var first = new LinkedList<int>(new[] { 1, 2, 3 });
var second = new LinkedList<int>(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<T> with no collection optimisation available.
static IEnumerable<int> 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
Expand Down