-
Notifications
You must be signed in to change notification settings - Fork 4
Optimize DefaultIfNotSingle loop #430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
c3f56c5
2993c6c
79a49d1
4909c58
3de2741
128aebf
bf4a05b
9b2502f
ad58295
5f03f64
fc0ed86
1284a4d
f78c22f
7cccbda
972bf0c
0990b09
669f446
65ff0e0
7c6fa95
ae2f96a
994abca
7c97fea
14c44f8
68d8648
6df8c51
b433321
caecc7a
a8eb22b
ec8564d
b197af1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| using System; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs
|
||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| internal static class DefaultIfNotSingleBaseline | ||
|
Check warning on line 8 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleBaseline.cs
|
||
| { | ||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate) | ||
| { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ArgumentNullException.ThrowIfNull(source); | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| bool found = false; | ||
| T? item = default; | ||
| foreach (T element in source.Where(predicate)) | ||
| { | ||
| if (found) | ||
| { | ||
| return default; | ||
| } | ||
|
|
||
| found = true; | ||
| item = element; | ||
| } | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this IEnumerable<T> source) | ||
| => source.DefaultIfNotSingleBaselineMethod(static _ => true); | ||
|
|
||
| public static T? DefaultIfNotSingleBaselineMethod<T>(this ImmutableArray<T> source, Func<T, bool> predicate) | ||
| => source.AsEnumerable().DefaultIfNotSingleBaselineMethod(predicate); | ||
| #pragma warning restore ECS0900 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| using System.Linq; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableBenchmarks.cs
|
||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleEnumerableBenchmarks | ||
| { | ||
| private readonly IEnumerable<int> _source = Enumerable.Range(0, 100).ToArray(); | ||
|
|
||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(x => x == 50); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(x => x == 50); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| using System.Linq; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleEnumerableNoPredicateBenchmarks.cs
|
||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleEnumerableNoPredicateBenchmarks | ||
| { | ||
| private readonly IEnumerable<int> _source = new[] { 0 }; | ||
|
|
||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| using System.Collections.Immutable; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleImmutableArrayBenchmarks.cs
|
||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| [MemoryDiagnoser] | ||
| [InProcess] | ||
| public class DefaultIfNotSingleImmutableArrayBenchmarks | ||
| { | ||
| private readonly ImmutableArray<int> _source = ImmutableArray.CreateRange(Enumerable.Range(0, 100)); | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| [Benchmark(Baseline = true)] | ||
| public int? Baseline() => _source.DefaultIfNotSingleBaselineMethod(x => x == 50); | ||
|
|
||
| [Benchmark] | ||
| public int? Optimized() => _source.DefaultIfNotSingleOptimizedMethod(x => x == 50); | ||
| #pragma warning restore ECS0900 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| using System; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs
|
||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
|
|
||
| namespace Moq.Analyzers.Benchmarks; | ||
|
|
||
| #pragma warning disable ECS0900 // Minimize boxing and unboxing | ||
| internal static class DefaultIfNotSingleOptimized | ||
|
Check warning on line 8 in tests/Moq.Analyzers.Benchmarks/DefaultIfNotSingleOptimized.cs
|
||
| { | ||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this IEnumerable<T> source, Func<T, bool> predicate) | ||
| { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ArgumentNullException.ThrowIfNull(source); | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| bool found = false; | ||
| T? item = default; | ||
| foreach (T element in source) | ||
| { | ||
| if (!predicate(element)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| if (found) | ||
| { | ||
| return default; | ||
| } | ||
|
|
||
| found = true; | ||
| item = element; | ||
| } | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this IEnumerable<T> source) | ||
| => source.DefaultIfNotSingleOptimizedMethod(static _ => true); | ||
|
|
||
| public static T? DefaultIfNotSingleOptimizedMethod<T>(this ImmutableArray<T> source, Func<T, bool> predicate) | ||
| => source.AsEnumerable().DefaultIfNotSingleOptimizedMethod(predicate); | ||
| #pragma warning restore ECS0900 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,76 +1,160 @@ | ||
| namespace Moq.Analyzers.Test.Common; | ||
| using System; | ||
|
Check failure on line 1 in tests/Moq.Analyzers.Test/Common/EnumerableExtensionsTests.cs
|
||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using Xunit; | ||
|
|
||
| namespace Moq.Analyzers.Test.Common; | ||
|
|
||
| public class EnumerableExtensionsTests | ||
| { | ||
| [Fact] | ||
| public void DefaultIfNotSingle_ReturnsNull_WhenSourceIsEmpty() | ||
| { | ||
| IEnumerable<int> source = []; | ||
| int result = source.DefaultIfNotSingle(); | ||
| Assert.Equal(0, result); | ||
| IEnumerable<object> source = []; | ||
| object? result = source.DefaultIfNotSingle(); | ||
| Assert.Null(result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ReturnsElement_WhenSourceContainsSingleElement() | ||
| { | ||
| int[] source = [42]; | ||
| int result = source.DefaultIfNotSingle(); | ||
| int? result = source.DefaultIfNotSingle(); | ||
| Assert.Equal(42, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ReturnsNull_WhenSourceContainsMultipleElements() | ||
| { | ||
| int[] source = [1, 2, 3]; | ||
| int result = source.DefaultIfNotSingle(); | ||
| int? result = source.DefaultIfNotSingle(); | ||
| Assert.Equal(0, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_WithPredicate_ReturnsNull_WhenNoElementsMatch() | ||
| { | ||
| int[] source = [1, 2, 3]; | ||
| int result = source.DefaultIfNotSingle(x => x > 10); | ||
| int? result = source.DefaultIfNotSingle(x => x > 10); | ||
| Assert.Equal(0, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_WithPredicate_ReturnsElement_WhenOnlyOneMatches() | ||
| { | ||
| int[] source = [1, 2, 3]; | ||
| int result = source.DefaultIfNotSingle(x => x == 2); | ||
| int? result = source.DefaultIfNotSingle(x => x == 2); | ||
| Assert.Equal(2, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_WithPredicate_ReturnsNull_WhenMultipleElementsMatch() | ||
| { | ||
| int[] source = [1, 2, 2, 3]; | ||
| int result = source.DefaultIfNotSingle(x => x > 1); | ||
| int? result = source.DefaultIfNotSingle(x => x > 1); | ||
| Assert.Equal(0, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ImmutableArray_ReturnsNull_WhenEmpty() | ||
| { | ||
| ImmutableArray<int> source = ImmutableArray<int>.Empty; | ||
| int result = source.DefaultIfNotSingle(x => x > 0); | ||
| int? result = source.DefaultIfNotSingle(x => x > 0); | ||
| Assert.Equal(0, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ImmutableArray_ReturnsElement_WhenSingleMatch() | ||
| { | ||
| ImmutableArray<int> source = [.. new[] { 5, 10, 15 }]; | ||
| int result = source.DefaultIfNotSingle(x => x == 10); | ||
| int? result = source.DefaultIfNotSingle(x => x == 10); | ||
| Assert.Equal(10, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ImmutableArray_ReturnsNull_WhenMultipleMatches() | ||
| { | ||
| ImmutableArray<int> source = [.. new[] { 5, 10, 10, 15 }]; | ||
| int result = source.DefaultIfNotSingle(x => x > 5); | ||
| int? result = source.DefaultIfNotSingle(x => x > 5); | ||
| Assert.Equal(0, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_StopsEnumeratingAfterSecondMatch() | ||
| { | ||
| CountingEnumerable<int> source = new(new[] { 1, 2, 3, 4 }); | ||
| int? result = source.DefaultIfNotSingle(x => x > 1); | ||
| Assert.Equal(0, result); | ||
|
rjmurillo marked this conversation as resolved.
|
||
| Assert.Equal(3, source.Count); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_String_ThrowsArgumentNullException_WhenPredicateIsNull() | ||
| { | ||
| IEnumerable<string> source = new List<string> { "a", "b", "c" }; | ||
| ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => source.DefaultIfNotSingle(null!)); | ||
| Assert.Equal("predicate", ex.ParamName); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| [Fact] | ||
| public void DefaultIfNotSingle_ImmutableArray_String_ThrowsArgumentNullException_WhenPredicateIsNull() | ||
| { | ||
| ImmutableArray<string> source = ImmutableArray.Create("a", "b", "c"); | ||
| Assert.Throws<ArgumentNullException>(() => source.DefaultIfNotSingle(null!)); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| [Fact] | ||
| public void CountingEnumerable_Count_Resets_OnEachEnumeration() | ||
| { | ||
| // The Count property resets to 0 every time GetEnumerator() is called. | ||
| // This means if enumeration is started but not completed, Count will reset on the next enumeration. | ||
| // This test verifies that behavior explicitly. | ||
| CountingEnumerable<int> source = new CountingEnumerable<int>(new[] { 1, 2, 3 }); | ||
|
|
||
| // First enumeration (partial) | ||
| using (IEnumerator<int> enumerator = source.GetEnumerator()) | ||
| { | ||
| Assert.Equal(0, source.Count); // Not started | ||
| Assert.True(enumerator.MoveNext()); // 1 | ||
| Assert.Equal(1, source.Count); | ||
|
|
||
| // Do not complete enumeration | ||
| } | ||
|
|
||
| // Second enumeration (full) | ||
| List<int> items = new List<int>(); | ||
| foreach (int item in source) | ||
| { | ||
| items.Add(item); | ||
| } | ||
|
|
||
| Assert.Equal(3, source.Count); // Count resets and counts all items | ||
| Assert.Equal(new[] { 1, 2, 3 }, items); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| private sealed class CountingEnumerable<T>(IEnumerable<T> items) : IEnumerable<T> | ||
| { | ||
| private readonly IEnumerable<T> _items = items; | ||
|
|
||
| /// <summary> | ||
| /// Gets tracks the number of items enumerated. Resets to 0 every time <see cref="GetEnumerator"/> is called. | ||
| /// This means if enumeration is started but not completed, <see cref="Count"/> will reset on the next enumeration. | ||
| /// This behavior is intentional for test scenarios that need to track enumeration per run. | ||
| /// </summary> | ||
| public int Count { get; private set; } | ||
|
|
||
| public IEnumerator<T> GetEnumerator() | ||
| { | ||
| // Reset count on every new enumeration. This can cause Count to reset if enumeration is started but not completed. | ||
| Count = 0; | ||
| foreach (T item in _items) | ||
| { | ||
| Count++; | ||
| yield return item; | ||
| } | ||
| } | ||
|
|
||
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.