Skip to content

Conversation

Joy-less
Copy link
Contributor

@Joy-less Joy-less commented Oct 2, 2025

Closes #120015 proposal.

Adds the following APIs with refs and tests:

namespace System
{
    public partial sealed class String
    {
        public int IndexOf(Rune value, int startIndex, StringComparison comparisonType);
        public int IndexOf(Rune value, int startIndex, int count, StringComparison comparisonType);
        public int IndexOf(char value, int startIndex, StringComparison comparisonType);
        public int IndexOf(char value, int startIndex, int count, StringComparison comparisonType);

        public int LastIndexOf(Rune value, int startIndex, StringComparison comparisonType);
        public int LastIndexOf(Rune value, int startIndex, int count, StringComparison comparisonType);
        public int LastIndexOf(char value, int startIndex, StringComparison comparisonType);
        public int LastIndexOf(char value, int startIndex, int count, StringComparison comparisonType);
        public int LastIndexOf(char value, StringComparison comparisonType);
    }

    public readonly struct Char
    {
        public bool Equals(char right, StringComparison comparisonType);
    }
}

NOTE:
This pull request ignores the two amendments listed in #120015 (comment). It should not be merged until those amendments are approved/rejected.

@tarekgh

@Copilot Copilot AI review requested due to automatic review settings October 2, 2025 01:13
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 2, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 2, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds new String and Char overloads to support searching for char and Rune with StringComparison and startIndex/count parameters, plus a Char.Equals overload with StringComparison. Updates reference assembly, core implementations, span helpers, and expands tests accordingly.

  • Adds IndexOf/LastIndexOf overloads for char and Rune with (startIndex, comparisonType) and (startIndex, count, comparisonType).
  • Exposes Char.Equals(char, StringComparison) publicly and adds corresponding tests.
  • Implements ordinal ignore-case search helpers and supporting SpanHelpers last-index routines.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
System.Runtime.Tests/System/StringTests.cs Updated and expanded tests for new IndexOf/LastIndexOf overloads (char and Rune) with startIndex/count + comparison.
System.Runtime.Tests/System/CharTests.cs Added tests for new Char.Equals with StringComparison and adjusted existing equality tests.
System.Runtime/ref/System.Runtime.cs Added new public API surface for String and Char overloads.
System.Private.CoreLib/src/System/String.Searching.cs Implemented new overloads, publicized Rune variants, and added ordinal ignore-case logic for ranged search.
System.Private.CoreLib/src/System/SpanHelpers.T.cs Added LastIndexOfChar / LastIndexOfAnyChar helpers for performance support.
System.Private.CoreLib/src/System/Char.cs Made Char.Equals(char, StringComparison) public with XML docs.

@Joy-less
Copy link
Contributor Author

Joy-less commented Oct 2, 2025

@tarekgh I will continue working on this tomorrow hopefully. I have sort of lost track of your reviews here, so summarising/updating them would be nice :)

@tarekgh
Copy link
Member

tarekgh commented Oct 2, 2025

I will continue working on this tomorrow hopefully. I have sort of lost track of your reviews here, so summarising/updating them would be nice :)

Thanks for your effort. no rush too. If you like, ignore all my comments (close them) and I'll take a full review again after you have the latest update, so you don't have to worry about missing anything.

@Joy-less
Copy link
Contributor Author

Joy-less commented Oct 3, 2025

I have encountered a consistency problem with empty strings. @tarekgh

If you choose an out-of-bounds index with IndexOf, it is allowed for both char and string versions as long as the startIndex equals the Length, which is consistent.

Console.WriteLine("".IndexOf('b', 0)); // -1
Console.WriteLine("".IndexOf("b", 0)); // -1

Console.WriteLine("a".IndexOf('b', 1)); // -1
Console.WriteLine("a".IndexOf("b", 1)); // -1

Console.WriteLine("a".IndexOf('b', 2)); // exception (startIndex > Length)
Console.WriteLine("a".IndexOf("b", 2)); // exception (startIndex > Length)

But if you choose an out-of-bounds index with LastIndexOf, it is allowed for the string version as long as the startIndex equals the Length, but it is only allowed for the char version if the Length equals 0 (in which case the startIndex and count are entirely ignored).

Console.WriteLine("".LastIndexOf('b', 0)); // -1
Console.WriteLine("".LastIndexOf("b", 0)); // -1

Console.WriteLine("a".LastIndexOf('b', 1)); // exception (startIndex >= Length)
Console.WriteLine("a".LastIndexOf("b", 1)); // -1

Console.WriteLine("a".LastIndexOf('b', 2)); // exception (startIndex >= Length)
Console.WriteLine("a".LastIndexOf("b", 2)); // exception (startIndex > Length)

This leaves us with a few choices:

  1. Make LastIndexOf(char, int, StringComparison) allow the startIndex to equal the Length. This means the behaviour will be inconsistent between LastIndexOf(char, int, StringComparison) and LastIndexOf(char, int).
  2. Make LastIndexOf(char, int, StringComparison) throw when the startIndex equals the Length. This means "".LastIndexOf((char)'\0', 0, stringComparison) will always throw even though "".LastIndexOf((string)"\0", 0, stringComparison) returns 0 or -1. Also, what should we do with "".LastIndexOf((char)'\0', stringComparison)?
  3. Modify LastIndexOf(char, int) to allow the startIndex to equal the Length. This might be a breaking change.

@tarekgh
Copy link
Member

tarekgh commented Oct 3, 2025

We need to figure out if this is going to cause any issue with F# like the previous added APIs did.

@tarekgh
Copy link
Member

tarekgh commented Oct 3, 2025

It shouldn't, since the IndexOf/LastIndexOf additions with char and Rune versions already had string versions. As for the char.Equals(char, StringComparison), there is no other char.Equals overload with 2 arguments.

My understanding it will break F#. For example:

We are adding

        public int LastIndexOf(char value, System.StringComparison comparisonType) 

which will conflict with the existing one

public int LastIndexOf(char value, int startIndex)

@Joy-less
Copy link
Contributor Author

Joy-less commented Oct 3, 2025

It shouldn't, since the IndexOf/LastIndexOf additions with char and Rune versions already had string versions. As for the char.Equals(char, StringComparison), there is no other char.Equals overload with 2 arguments.

My understanding it will break F#. For example:

We are adding

        public int LastIndexOf(char value, System.StringComparison comparisonType) 

which will conflict with the existing one

public int LastIndexOf(char value, int startIndex)

Yeah, sorry I deleted that comment after understanding it may break the code. But if we don't accept these changes, this limitation would make it almost impossible to add new overloads into .NET due to F#'s type inference.

@tarekgh
Copy link
Member

tarekgh commented Oct 3, 2025

Let's hold on this PR till we resolve the F# issue in general.

@tarekgh tarekgh added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Oct 3, 2025
@tarekgh
Copy link
Member

tarekgh commented Oct 3, 2025

@Joy-less let us not stop continuing updating this PR so we can be ready when we decide to merge. Thanks!

@Joy-less
Copy link
Contributor Author

Joy-less commented Oct 3, 2025

@Joy-less let us not stop continuing updating this PR so we can be ready when we decide to merge. Thanks!

Understood, please could you help with this issue if it is clear? #120314 (comment)

@tarekgh
Copy link
Member

tarekgh commented Oct 3, 2025

Understood, please could you help with this issue if it is clear? #120314 (comment)

Let’s proceed with the following behavior:

  • LastIndexOf(char, int, StringComparison) should throw when startIndex equals or greater than the string’s Length.
  • For an empty string "".LastIndexOf(...) should always return -1, regardless of the search value.

I chose this behavior for the following reasons:

  1. We’re not fully consistent today, so let’s do it correctly for the new APIs.
  2. A startIndex that is negative or greater than or equal to Length is an invalid index within the string — this is logically correct.
  3. We should never return an index from IndexOf or LastIndexOf that falls outside the valid range of the string. For an empty string, the correct result is always -1. Returning 0 could cause the caller to access the string at that index and encounter an exception.

@Joy-less
Copy link
Contributor Author

Joy-less commented Oct 4, 2025

@tarekgh I have added the if (Length == 0) { return -1; } checks. Please let me know what I need to fix in the pull request now, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Runtime community-contribution Indicates that the PR has been added by a community member NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add missing overloads to "Flow System.Text.Rune through more APIs" proposal
2 participants