-
Couldn't load subscription status.
- Fork 5.2k
StringBuilder.Replace with ReadOnlySpan<char> #93938
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 all commits
219e3fe
b5a76f7
eac03fd
eb5deae
184d74f
17acae0
b9bc86d
570e380
acd972a
12bbf2a
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 |
|---|---|---|
|
|
@@ -1841,6 +1841,17 @@ private StringBuilder AppendFormat<TArg0, TArg1, TArg2>(IFormatProvider? provide | |
| /// </remarks> | ||
| public StringBuilder Replace(string oldValue, string? newValue) => Replace(oldValue, newValue, 0, Length); | ||
|
|
||
| /// <summary> | ||
| /// Replaces all instances of one read-only character span with another in this builder. | ||
| /// </summary> | ||
| /// <param name="oldValue">The read-only character span to replace.</param> | ||
| /// <param name="newValue">The read-only character span to replace <paramref name="oldValue"/> with.</param> | ||
| /// <remarks> | ||
| /// If <paramref name="newValue"/> is empty, instances of <paramref name="oldValue"/> | ||
| /// are removed from this builder. | ||
| /// </remarks> | ||
| public StringBuilder Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue) => Replace(oldValue, newValue, 0, Length); | ||
|
|
||
| /// <summary> | ||
| /// Determines if the contents of this builder are equal to the contents of another builder. | ||
| /// </summary> | ||
|
|
@@ -1950,6 +1961,23 @@ public bool Equals(ReadOnlySpan<char> span) | |
| /// are removed from this builder. | ||
| /// </remarks> | ||
| public StringBuilder Replace(string oldValue, string? newValue, int startIndex, int count) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(oldValue); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can just be ThrowIfNull. The called method has the check for empty. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would change the exception message (I think) in case it's an empty string ( |
||
| return Replace(oldValue.AsSpan(), newValue.AsSpan(), startIndex, count); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Replaces all instances of one read-only character span with another in part of this builder. | ||
| /// </summary> | ||
| /// <param name="oldValue">The read-only character span to replace.</param> | ||
| /// <param name="newValue">The read-only character span to replace <paramref name="oldValue"/> with.</param> | ||
| /// <param name="startIndex">The index to start in this builder.</param> | ||
| /// <param name="count">The number of characters to read in this builder.</param> | ||
| /// <remarks> | ||
| /// If <paramref name="newValue"/> is empty, instances of <paramref name="oldValue"/> | ||
| /// are removed from this builder. | ||
| /// </remarks> | ||
| public StringBuilder Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue, int startIndex, int count) | ||
| { | ||
| int currentLength = Length; | ||
| if ((uint)startIndex > (uint)currentLength) | ||
|
|
@@ -1960,9 +1988,10 @@ public StringBuilder Replace(string oldValue, string? newValue, int startIndex, | |
| { | ||
| throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_IndexMustBeLessOrEqual); | ||
| } | ||
| ArgumentException.ThrowIfNullOrEmpty(oldValue); | ||
|
|
||
| newValue ??= string.Empty; | ||
| if (oldValue.Length == 0) | ||
| { | ||
| throw new ArgumentException(SR.Arg_EmptySpan, nameof(oldValue)); | ||
| } | ||
|
|
||
| var replacements = new ValueListBuilder<int>(stackalloc int[128]); // A list of replacement positions in a chunk to apply | ||
|
|
||
|
|
@@ -2225,7 +2254,7 @@ private void Insert(int index, ref char value, int valueCount) | |
| /// <remarks> | ||
| /// This routine is very efficient because it does replacements in bulk. | ||
| /// </remarks> | ||
| private void ReplaceAllInChunk(ReadOnlySpan<int> replacements, StringBuilder sourceChunk, int removeCount, string value) | ||
| private void ReplaceAllInChunk(ReadOnlySpan<int> replacements, StringBuilder sourceChunk, int removeCount, ReadOnlySpan<char> value) | ||
| { | ||
| Debug.Assert(!replacements.IsEmpty); | ||
|
|
||
|
|
@@ -2251,7 +2280,7 @@ private void ReplaceAllInChunk(ReadOnlySpan<int> replacements, StringBuilder sou | |
| while (true) | ||
| { | ||
| // Copy in the new string for the ith replacement | ||
| ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, ref value.GetRawStringData(), value.Length); | ||
| ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, ref MemoryMarshal.GetReference<char>(value), value.Length); | ||
| int gapStart = replacements[i] + removeCount; | ||
| i++; | ||
| if ((uint)i >= replacements.Length) | ||
|
|
@@ -2289,7 +2318,7 @@ private void ReplaceAllInChunk(ReadOnlySpan<int> replacements, StringBuilder sou | |
| /// <param name="indexInChunk">The index in <paramref name="chunk"/> at which the substring starts.</param> | ||
| /// <param name="count">The logical count of the substring.</param> | ||
| /// <param name="value">The prefix.</param> | ||
| private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, string value) | ||
| private bool StartsWith(StringBuilder chunk, int indexInChunk, int count, ReadOnlySpan<char> value) | ||
| { | ||
| for (int i = 0; i < value.Length; i++) | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Xunit; | ||
|
|
||
| namespace System.Text.Tests | ||
| { | ||
| public abstract class StringBuilderReplaceTests | ||
| { | ||
| [Theory] | ||
| [InlineData("", "a", "!", 0, 0, "")] | ||
| [InlineData("aaaabbbbccccdddd", "a", "!", 0, 16, "!!!!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "a", "!", 2, 3, "aa!!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "a", "!", 4, 1, "aaaabbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aab", "!", 2, 2, "aaaabbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aab", "!", 2, 3, "aa!bbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aa", "!", 0, 16, "!!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aa", "$!", 0, 16, "$!$!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aa", "$!$", 0, 16, "$!$$!$bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aaaa", "!", 0, 16, "!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aaaa", "$!", 0, 16, "$!bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "a", "", 0, 16, "bbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "b", null, 0, 16, "aaaaccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aaaabbbbccccdddd", "", 0, 16, "")] | ||
| [InlineData("aaaabbbbccccdddd", "aaaabbbbccccdddd", "", 16, 0, "aaaabbbbccccdddd")] | ||
| [InlineData("aaaabbbbccccdddd", "aaaabbbbccccdddde", "", 0, 16, "aaaabbbbccccdddd")] | ||
| [InlineData("aaaaaaaaaaaaaaaa", "a", "b", 0, 16, "bbbbbbbbbbbbbbbb")] | ||
| public void Replace_StringBuilder(string value, string oldValue, string newValue, int startIndex, int count, string expected) | ||
| { | ||
| StringBuilder builder; | ||
| if (startIndex == 0 && count == value.Length) | ||
| { | ||
| // Use Replace(string, string) / Replace(ReadOnlySpan<char>, ReadOnlySpan<char>) | ||
| builder = new StringBuilder(value); | ||
| Replace(builder, oldValue, newValue); | ||
| Assert.Equal(expected, builder.ToString()); | ||
| } | ||
|
|
||
| // Use Replace(string, string, int, int) / Replace(ReadOnlySpan<char>, ReadOnlySpan<char>, int, int) | ||
| builder = new StringBuilder(value); | ||
| Replace(builder, oldValue, newValue, startIndex, count); | ||
| Assert.Equal(expected, builder.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Replace_StringBuilderWithMultipleChunks() | ||
| { | ||
| StringBuilder builder = StringBuilderTests.StringBuilderWithMultipleChunks(); | ||
| Replace(builder, "a", "b", builder.Length - 10, 10); | ||
| Assert.Equal(new string('a', builder.Length - 10) + new string('b', 10), builder.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Replace_StringBuilderWithMultipleChunks_WholeString() | ||
| { | ||
| StringBuilder builder = StringBuilderTests.StringBuilderWithMultipleChunks(); | ||
| Replace(builder, builder.ToString(), ""); | ||
| Assert.Same(string.Empty, builder.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Replace_StringBuilderWithMultipleChunks_LongString() | ||
| { | ||
| StringBuilder builder = StringBuilderTests.StringBuilderWithMultipleChunks(); | ||
| Replace(builder, builder.ToString() + "b", ""); | ||
| Assert.Equal(StringBuilderTests.s_chunkSplitSource, builder.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Replace_Invalid() | ||
| { | ||
| var builder = new StringBuilder(0, 5); | ||
| builder.Append("Hello"); | ||
|
|
||
| AssertExtensions.Throws<ArgumentException>("oldValue", () => Replace(builder, "", "a")); // Old value is empty | ||
| AssertExtensions.Throws<ArgumentException>("oldValue", () => Replace(builder, "", "a", 0, 0)); // Old value is empty | ||
|
|
||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("requiredLength", () => Replace(builder, "o", "oo")); // New length > builder.MaxCapacity | ||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("requiredLength", () => Replace(builder, "o", "oo", 0, 5)); // New length > builder.MaxCapacity | ||
|
|
||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => Replace(builder, "a", "b", -1, 0)); // Start index < 0 | ||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => Replace(builder, "a", "b", 0, -1)); // Count < 0 | ||
|
|
||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => Replace(builder, "a", "b", 6, 0)); // Count + start index > builder.Length | ||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => Replace(builder, "a", "b", 5, 1)); // Count + start index > builder.Length | ||
| AssertExtensions.Throws<ArgumentOutOfRangeException>("count", () => Replace(builder, "a", "b", 4, 2)); // Count + start index > builder.Length | ||
| } | ||
|
|
||
| protected abstract StringBuilder Replace(StringBuilder builder, string oldValue, string newValue); | ||
|
|
||
| protected abstract StringBuilder Replace(StringBuilder builder, string oldValue, string newValue, int startIndex, int count); | ||
| } | ||
|
|
||
| public class StringBuilderReplaceTests_String : StringBuilderReplaceTests | ||
| { | ||
| [Fact] | ||
| public void Replace_String_Invalid() | ||
| { | ||
| var builder = new StringBuilder(0, 5); | ||
| builder.Append("Hello"); | ||
|
|
||
| AssertExtensions.Throws<ArgumentNullException>("oldValue", () => Replace(builder, null, "")); // Old value is null | ||
| AssertExtensions.Throws<ArgumentNullException>("oldValue", () => Replace(builder, null, "a", 0, 0)); // Old value is null | ||
| } | ||
|
|
||
| protected override StringBuilder Replace(StringBuilder builder, string oldValue, string newValue) | ||
| => builder.Replace(oldValue, newValue); | ||
|
|
||
| protected override StringBuilder Replace(StringBuilder builder, string oldValue, string newValue, int startIndex, int count) | ||
| => builder.Replace(oldValue, newValue, startIndex, count); | ||
| } | ||
|
|
||
| public class StringBuilderReplaceTests_Span : StringBuilderReplaceTests | ||
| { | ||
| protected override StringBuilder Replace(StringBuilder builder, string oldValue, string newValue) | ||
| => builder.Replace(oldValue.AsSpan(), newValue.AsSpan()); | ||
|
|
||
| protected override StringBuilder Replace(StringBuilder builder, string oldValue, string newValue, int startIndex, int count) | ||
| => builder.Replace(oldValue.AsSpan(), newValue.AsSpan(), startIndex, count); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.