Skip to content

Conversation

@jnyrup
Copy link
Member

@jnyrup jnyrup commented Mar 2, 2025

Three little changes to move/remove some cruft when working with strings.

For the first commit I benchmarked the difference between several alternatives and the most primitive one was the fastest one.

Benchmark results
Method MemberName Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
RegexInstance Parent.Property2 820.262 ns 15.9691 ns 22.3865 ns 816.300 ns 1.001 0.04 0.3834 0.0019 2408 B 1.00
RegexStatic Parent.Property2 72.595 ns 0.9518 ns 0.7948 ns 72.406 ns 0.089 0.00 - - - 0.00
RegexCache Parent.Property2 64.338 ns 1.0634 ns 0.9947 ns 64.712 ns 0.078 0.00 - - - 0.00
RegexSource Parent.Property2 32.408 ns 0.6633 ns 0.7373 ns 31.989 ns 0.040 0.00 - - - 0.00
LinqAndPattern Parent.Property2 17.248 ns 0.2948 ns 0.2757 ns 17.262 ns 0.021 0.00 0.0051 - 32 B 0.01
IndexOfAny Parent.Property2 10.781 ns 0.0412 ns 0.0344 ns 10.770 ns 0.013 0.00 0.0051 - 32 B 0.01
IndexOfAnyCached Parent.Property2 6.062 ns 0.1416 ns 0.2365 ns 5.933 ns 0.007 0.00 - - - 0.00
Contains Parent.Property2 1.966 ns 0.0569 ns 0.0475 ns 1.974 ns 0.002 0.00 - - - 0.00
ContainsOrdinal Parent.Property2 2.396 ns 0.0143 ns 0.0127 ns 2.397 ns 0.003 0.00 - - - 0.00
RegexInstance Property1 783.972 ns 3.8892 ns 3.2477 ns 783.435 ns 1.000 0.01 0.3748 0.0019 2352 B 1.00
RegexStatic Property1 43.992 ns 0.1414 ns 0.1253 ns 44.002 ns 0.056 0.00 - - - 0.00
RegexCache Property1 41.948 ns 0.8558 ns 1.5432 ns 42.259 ns 0.054 0.00 - - - 0.00
RegexSource Property1 30.557 ns 0.6352 ns 0.9110 ns 30.647 ns 0.039 0.00 - - - 0.00
LinqAndPattern Property1 21.860 ns 0.1412 ns 0.1252 ns 21.860 ns 0.028 0.00 0.0051 - 32 B 0.01
IndexOfAny Property1 10.921 ns 0.2096 ns 0.1858 ns 10.897 ns 0.014 0.00 0.0051 - 32 B 0.01
IndexOfAnyCached Property1 5.801 ns 0.1231 ns 0.1152 ns 5.738 ns 0.007 0.00 - - - 0.00
Contains Property1 6.448 ns 0.0354 ns 0.0296 ns 6.444 ns 0.008 0.00 - - - 0.00
ContainsOrdinal Property1 6.694 ns 0.1196 ns 0.0999 ns 6.656 ns 0.009 0.00 - - - 0.00
Benchmark code
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.RegularExpressions;

BenchmarkRunner.Run<Benchmark>();

[MemoryDiagnoser]
public class Benchmark
{
    [Params("Property1", "Parent.Property2")]
    public string MemberName { get; set; }

    [Benchmark(Baseline = true)]
    public bool RegexInstance() => new Regex(@"[\.\[\]]").IsMatch(MemberName);

    [Benchmark]
    public bool RegexStatic() => Regex.IsMatch(MemberName, @"[\.\[\]]");

    private static readonly Regex regex = new Regex(@"[\.\[\]]");

    [Benchmark]
    public bool RegexCache() => regex.IsMatch(MemberName);

    [Benchmark]
    public bool RegexSource() => A.Contains().IsMatch(MemberName);

    [Benchmark]
    public bool LinqAndPattern() => MemberName.Any(e => e is '.' or '[' or ']');

    [Benchmark]
    public bool IndexOfAny() => MemberName.IndexOfAny(['.', '[', ']']) >= 0;

    private static readonly char[] chars = ['.', '[', ']'];

    [Benchmark]
    public bool IndexOfAnyCached() => MemberName.IndexOfAny(chars) >= 0;

    [Benchmark]
    public bool Contains() => MemberName.Contains('.') || MemberName.Contains('[') || MemberName.Contains(']');

    [Benchmark]
    public bool ContainsOrdinal() =>
        MemberName.Contains('.', StringComparison.Ordinal) || MemberName.Contains('[', StringComparison.Ordinal) || MemberName.Contains(']', StringComparison.Ordinal);
}

static partial class A
{
    [GeneratedRegex(@"[\.\[\]]")]
    public static partial Regex Contains();
}

The second and third commit uses better overloads of string.Replace and string.Split

IMPORTANT

  • If the PR touches the public API, the changes have been approved in a separate issue with the "api-approved" label.
  • The code complies with the Coding Guidelines for C#.
  • The changes are covered by unit tests which follow the Arrange-Act-Assert syntax and the naming conventions such as is used in these tests.
  • If the PR adds a feature or fixes a bug, please update the release notes with a functional description that explains what the change means to consumers of this library, which are published on the website.
  • If the PR changes the public API the changes needs to be included by running AcceptApiChanges.ps1 or AcceptApiChanges.sh.
  • If the PR affects the documentation, please include your changes in this pull request so the documentation will appear on the website.
    • Please also run ./build.sh --target spellcheck or .\build.ps1 --target spellcheck before pushing and check the good outcome

@github-actions
Copy link

github-actions bot commented Mar 2, 2025

Qodana for .NET

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at [email protected]

@coveralls
Copy link

Pull Request Test Coverage Report for Build 13617136846

Details

  • 9 of 11 (81.82%) changed or added relevant lines in 7 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.01%) to 97.407%

Changes Missing Coverage Covered Lines Changed/Added Lines %
Src/FluentAssertions/Polyfill/SystemExtensions.cs 0 2 0.0%
Totals Coverage Status
Change from base Build 13574966180: -0.01%
Covered Lines: 12327
Relevant Lines: 12513

💛 - Coveralls

Copy link
Member

@dennisdoomen dennisdoomen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title doesn't seem to cover the purpose of this PR, right? It's about performance optimizations and not about text cleanup.

@dennisdoomen dennisdoomen added this to the 8.2.0 milestone Mar 2, 2025
@jnyrup jnyrup changed the title Text cleanups Optimize various string operations Mar 2, 2025
@jnyrup
Copy link
Member Author

jnyrup commented Mar 2, 2025

The title doesn't seem to cover the purpose of this PR, right? It's about performance optimizations and not about text cleanup.

Thanks, updated the title to clarify what's going on.

@jnyrup jnyrup merged commit 0a649cb into fluentassertions:main Mar 2, 2025
8 checks passed
@jnyrup jnyrup deleted the text_cleanups branch March 2, 2025 16:33
This was referenced Dec 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants