2

I want to take and edit a string in-place in a .NET app. I know that StringBuilder allows me to do in-place appends, inserts, and replaces, but it does not allow an easy way of doing stuff like this:

while (script.IndexOf("@Unique", StringComparison.InvariantCultureIgnoreCase) != -1)
{
   int Location = script.IndexOf("@Unique", StringComparison.InvariantCultureIgnoreCase);
   script = script.Remove(Location, 7);
   script = script.Insert(Location, Guid.NewGuid().ToString());
}

As there is no IndexOf in StringBuilder. Does anyone have an effective way to do in-place editing of textual information?

Edit #1: Changed sample to make more obvious that each 'replace' needs to have a different result.

JasonRShaver
  • 4,344
  • 3
  • 32
  • 39

8 Answers8

5

If your code really is this straightforward then why not just use one of the built-in Replace methods, either on string, StringBuilder or Regex?

EDIT FOLLOWING COMMENT...

You can replace each occurrence with a separate value by using one of the overloads of Regex.Replace that takes a MatchEvaluator argument:

string foo = "blah blah @Unique blah @Unique blah blah @Unique blah";

// replace each occurrence of "@Unique" with a separate guid
string bar = Regex.Replace(foo, "@Unique",
    new MatchEvaluator(m => Guid.NewGuid().ToString()),
    RegexOptions.IgnoreCase));
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • My problem is that each instance of @Unique, in the above sample, needs a different value, so I can't do a global find-and-replace in the string. – JasonRShaver Oct 15 '09 at 16:19
  • @JasonRShaver: This can be done using `Regex.Replace`. Take a look at my edit. – LukeH Oct 15 '09 at 16:35
  • @Luke: This solution looks good, but does it support StringBuilder, as in the requirement. Guess "JasonRShaver" is able to do it with String as in the question. – noob.spt Oct 15 '09 at 16:48
  • This is a very interesting solution. I am curious of how well the regex processor would perform, memory wise, by doing this. The MatchEvaluator is also new to me and am glad to 'know' about it now. I will make a test app of this method later and see if it fully resolves the problem. – JasonRShaver Oct 15 '09 at 19:23
  • @JasonRShaver Those methods use `StringBuilder` internally (and do some sub-stringing) so should be fairly efficient. And I've just realised, necro-comment :-D – Adam Houldsworth Sep 11 '13 at 12:36
2

How about StringBuilder "Replace" method:

StringBuilder script;
script.Replace("@Unique", GetGuidString());
mihai
  • 4,592
  • 3
  • 29
  • 42
noob.spt
  • 971
  • 3
  • 15
  • 24
  • 2
    If the string has two @Unique's then both replacements will have the same value instead of unique values. That's why he wan't to use IndexOf so a new unique value is generated for each replacement. – Crispy Oct 15 '09 at 16:26
  • I don't think StringBuilder can be used in this scenario then. You have to process it as a String only, may be you can process it before adding it to StringBuilder. – noob.spt Oct 15 '09 at 16:32
2

How many replacements will you be doing?

If its not four figures, then just accept the new string instances, you may be prematurely optimising...

Another solution... Split on "@uniqueID" then rejoin with a StringBuilder adding your seperator for each iteration.

cjk
  • 45,739
  • 9
  • 81
  • 112
  • The system will be handling about 300,000 items at the peak we expect. – JasonRShaver Oct 15 '09 at 16:32
  • @Jason - and what's the performance specification for this method at peak load? Do an implementation and benchmark it at load. If it meets specs, ship it. If not, profile it. +1. – TrueWill Oct 15 '09 at 17:37
1

User Dennis has provided an IndexOf extension method for StringBuilder. With this, you should be able to use StringBuilder in this manner.

Community
  • 1
  • 1
Schmalls
  • 1,434
  • 1
  • 19
  • 19
1

StringBuilder is made so that you can easily add to it, but at the tradeoff that it's difficult to search in it - and especially, it's more difficult (i.e. slower) to index it. If you need to modify some characters "in-place", it's best to do it on the resulting string.

But it's difficult to know from your question what is the right answer for you, my feeling is that you shouldn't be needing in-place replacement in a StringBuilder, and the problem is somewhere else/you do something else wrong.

Virgil
  • 3,022
  • 2
  • 19
  • 36
0

Can you use a string split to do this efficiently?

Something like:

var sections = "a-@Unique-b-@Unique-c".Split(new string[] { "@Unique" }, StringSplitOptions.None);
int i;
StringBuilder builder = new StringBuilder();
for(i = 0; i < sections.Length - 1; i++)
{
    builder.Append(sections[i]);
    builder.Append(Guid.NewGuid().ToString());
}
builder.Append(sections[i]);

Console.WriteLine(builder.ToString());
Console.ReadKey(true);
John Gietzen
  • 48,783
  • 32
  • 145
  • 190
0

complex but should be performant solution

    public StringBuilder Replace(this StringBuilder sb, string toReplace, Func<string> getReplacement)
    {
        for (int i = 0; i < sb.Length; i++)
        {
            bool replacementFound = true;
            for (int toReplaceIndex = 0; toReplaceIndex < toReplace.Length; toReplaceIndex++)
            {
                int sbIndex = toReplaceIndex + i;
                if (sbIndex < sb.Length)
                {
                    return sb;
                }
                if (sb[sbIndex] != toReplace[toReplaceIndex])
                {
                    replacementFound = false;
                    break;
                }
            }
            if (replacementFound)
            {
                string replacement = getReplacement();
                // reuse the space of the toReplace string
                for (int replacementIndex = 0; replacementIndex < toReplace.Length && replacementIndex < replacement.Length; replacementIndex++)
                {
                    int sbIndex = replacementIndex + i;
                    sb[sbIndex] = replacement[i];
                }
                // remove toReplace string remainders
                if (replacement.Length < toReplace.Length)
                {
                    sb.Remove(i + replacement.Length, replacement.Length - toReplace.Length)
                }
                // insert chars not yet inserted
                if (replacement.Length > toReplace.Length)
                {
                    sb.Insert(i + toReplace.Length, replacement.ToCharArray(toReplace.Length, toReplace.Length - replacement.Length));
                }
            }
        }
        return sb;
    }

use case

var sb = new StringBuilder(script);

script = sb.Replace("@Unique", () => Guid.NewGuid().ToString()).ToString();
Firo
  • 30,626
  • 4
  • 55
  • 94
-4

You are going to need to use an unmanaged code block As simple as declare a pointer to your string and manipulate it in memory.

Example

unsafe
{
  char* ip;
  ip = &to_your_string;
}
theKing
  • 1,616
  • 4
  • 18
  • 23
  • It looks like unsafe does no allow you to declare a pointer to a string. Sorry :( – theKing Oct 15 '09 at 17:26
  • 1
    -1. That is the most frightening bit of C# code I have seen in months. I would never even **consider** doing such a thing. I'm pleased the .NET designers insured that it would not work. – TrueWill Oct 15 '09 at 17:33
  • 1
    @TrueWill: Unfortunately it's quite easy to mutate an existing string. The `string` class has private `AppendInPlace`, `InsertInPlace` and `RemoveInPlace` methods that can be called using reflection. But I agree with you that to even consider using those methods in any real-world code would be crazy. – LukeH Oct 16 '09 at 09:31
  • 1
    Sorry TrueWill I didn't mean to make you cry... but unsafe is part of the .net framework and the .NET designers are the ones designed it. It may not work with strings that is a different story. – theKing Oct 24 '09 at 21:03