Why doesn't StringBuilder have IndexOf method?
Asked Answered
D

4

27

I understand that I can call ToString().IndexOf(...), but I don't want to create an extra string. I understand that I can write a search routine manually. I just wonder why such a routine doesn't already exist in the framework.

Deviltry answered 31/8, 2009 at 23:55 Comment(0)
P
9

Unfortunately, many of the methods implemented for String could have been implemented for StringBuilder but that was not done. Consider using extension methods to add what you care about.

Pit answered 31/8, 2009 at 23:57 Comment(0)
D
60

I know this is an old question, however I have written a extension method that performs an IndexOf on a StringBuilder. It is below. I hope it helps anyone that finds this question, either from a Google search or searching StackOverflow.

/// <summary>
/// Returns the index of the start of the contents in a StringBuilder
/// </summary>        
/// <param name="value">The string to find</param>
/// <param name="startIndex">The starting index.</param>
/// <param name="ignoreCase">if set to <c>true</c> it will ignore case</param>
/// <returns></returns>
public static int IndexOf(this StringBuilder sb, string value, int startIndex, bool ignoreCase)
{            
    int index;
    int length = value.Length;
    int maxSearchLength = (sb.Length - length) + 1;

    if (ignoreCase)
    {
        for (int i = startIndex; i < maxSearchLength; ++i)
        {
            if (Char.ToLower(sb[i]) == Char.ToLower(value[0]))
            {
                index = 1;
                while ((index < length) && (Char.ToLower(sb[i + index]) == Char.ToLower(value[index])))
                    ++index;

                if (index == length)
                    return i;
            }
        }

        return -1;
    }

    for (int i = startIndex; i < maxSearchLength; ++i)
    {
        if (sb[i] == value[0])
        {
            index = 1;
            while ((index < length) && (sb[i + index] == value[index]))
                ++index;

            if (index == length)
                return i;
        }
    }

    return -1;
}
Dachia answered 6/7, 2011 at 18:29 Comment(7)
It's said Use the String.ToUpperInvariant method instead of the String.ToLowerInvariant method when you normalize strings for comparison. I guess this is also true for characters comparison: Char.ToUpper and Char.ToLower work similarly to the String.ToUpper and String.ToLower methods described in the previous sectionFinnougrian
Fair call; feel free to make the edits to the above post.Dachia
Sorry, I was wrong. Actually Microsoft has optimized ToUpperInvariant(), not ToUpper(). In this case, we can't provide ToUpperInvariant() by default as far as it can lead to the wrong comparison in some languages like Turkish. So, in this regard, your code is absolutely correct.Finnougrian
It's ok to be wrong. Thanks for coming back and commenting again. :+1:Dachia
@Sergey Isn't it the other way around: ToUpperInvariant should be used because it correctly upper-cases the Turkish-I whereas ToLower and ToLowerInvariant is incorrect?Smoot
@Smoot In my second comment I said that ToUpperInvariant() is optimized but cannot be used for Turkish. Only ToLower() & ToUpper() will provide the correct result. >>> dotnetfiddle.net/YNO52f <<< So, I can't agree that ToUpperInvariant should be used because it correctly upper-cases the Turkish-I.Finnougrian
Edited ToLower() -> to ToUpper() for performance reasons mentioned above (pending approval). I would have chosen ToUpperInvariant(), but some above said it didn't work for them as expected (eg, for Turkish).Divisibility
P
9

Unfortunately, many of the methods implemented for String could have been implemented for StringBuilder but that was not done. Consider using extension methods to add what you care about.

Pit answered 31/8, 2009 at 23:57 Comment(0)
J
5

Dennis, great solution. Thanks. I suggest to optimize it a little:

public static int IndexOf(
    this StringBuilder sb,
    string value,
    int startIndex,
    bool ignoreCase)
{
    int len = value.Length;
    int max = (sb.Length - len) + 1;
    var v1 = (ignoreCase)
        ? value.ToLower() : value;
    var func1 = (ignoreCase)
        ? new Func<char, char, bool>((x, y) => char.ToLower(x) == y)
        : new Func<char, char, bool>((x, y) => x == y);
    for (int i1 = startIndex; i1 < max; ++i1)
        if (func1(sb[i1], v1[0]))
        {
            int i2 = 1;
            while ((i2 < len) && func1(sb[i1 + i2], v1[i2]))
                ++i2;
            if (i2 == len)
                return i1;
        }
    return -1;
}
Jeannajeanne answered 27/11, 2020 at 16:2 Comment(0)
P
-2

Calling ToString() on a StringBuilder doesn't create an extra object, confusingly. Internally, StringBuilder stores a String object, for performance; calling ToString() simply returns that object.

Pandich answered 1/9, 2009 at 0:1 Comment(7)
this is not correct in the context of the question if the stringbuilder is asked to modify itself Tthen a new string is created, the mutability of the internal buffer is not exposed to managed code.Sherl
@ ShuggyCoUk: my comment was a little glib. I've removed it.Pandich
To clarify the previous comment, there is not much overhead from calling ToString. But after you call it, the next modification to the StringBuilder will incur copying overhead. (This is a valid optimzation because ToString is usually the last thing done to a StringBuilder.) As a result of this, efficient implementations of String-like methods can't use ToString, which precludes a trivial solution to the original poster's issue.Pit
@binarycoder: Is that optimization still performed? I thought StringBuilder.ToString was changed to produce a new String object unless it can reuse a string that has already been returned, to avoid the possibility of one thread calling ToString while another thread is mutating the StringBuilder. While there's no spec as to what string should be returned in that case, there shouldn't be any way by which the string which is returned can be modified after the return; the only way I know to ensure that is to return a string which has never and will never be open to modification.Amir
This answer is misleading as it suggests that the ToString method simply returns an already available string. That is not the case. There is some weird code using wstrcpy(looked at the decompiled code in ILSpy, .NET4) to create a new string from this stringbuilder.Armour
Hmm. I'll peek at the source when I'm next near a computer. Thanks, @Tim.Pandich
Here is source: referencesource.microsoft.com/#mscorlib/system/text/… That is clearly not a no-op.Armour

© 2022 - 2024 — McMap. All rights reserved.