Skip to content

Commit 2b84a33

Browse files
committed
Added EnumerateStringBuilder sample
1 parent 98cc300 commit 2b84a33

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net7.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
12+
</ItemGroup>
13+
14+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnumerateStringBuilder", "EnumerateStringBuilder.csproj", "{733A44FC-57DB-4556-8A03-6B47566FA9F4}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{733A44FC-57DB-4556-8A03-6B47566FA9F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{733A44FC-57DB-4556-8A03-6B47566FA9F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{733A44FC-57DB-4556-8A03-6B47566FA9F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{733A44FC-57DB-4556-8A03-6B47566FA9F4}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal

EnumerateStringBuilder/Program.cs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
using BenchmarkDotNet.Running;
4+
5+
BenchmarkRunner.Run<Benchmark>();
6+
7+
[MemoryDiagnoser]
8+
public class Benchmark
9+
{
10+
private StringBuilder _sb = default!;
11+
12+
[Params(10, 100, 1000)]
13+
public int Chars { get; set; }
14+
15+
[GlobalSetup]
16+
public void Setup() => _sb = new StringBuilder(new string('c', Chars));
17+
18+
[Benchmark(Baseline = true)]
19+
public void EnumeratorViaToString()
20+
{
21+
foreach (var c in _sb.ToString()) _ = c;
22+
}
23+
24+
[Benchmark]
25+
public void EnumeratorViaExtension()
26+
{
27+
foreach (var c in _sb) _ = c;
28+
}
29+
30+
[Benchmark]
31+
public void IterateViaForLoop()
32+
{
33+
for (var i = 0; i < _sb.Length; i++)
34+
_ = _sb[i];
35+
}
36+
37+
[Benchmark]
38+
public void ViaChunkEnumerator()
39+
{
40+
foreach (var chunk in _sb.GetChunks())
41+
{
42+
foreach (var c in chunk.Span)
43+
_ = c;
44+
}
45+
}
46+
}
47+
48+
public static class Extensions
49+
{
50+
public static StringEnumerator GetEnumerator(this StringBuilder s) => new(s);
51+
}
52+
53+
/// <summary>
54+
/// ref structs can't escape to the heap
55+
/// We do this to further optimize the runtime and allocations
56+
/// I know that mutable value types are evil, but normally this type
57+
/// is not handled by user-code, so it is fine
58+
/// </summary>
59+
public ref struct StringEnumerator
60+
{
61+
private readonly StringBuilder _stringBuilder;
62+
private int _index = -1;
63+
public StringEnumerator(StringBuilder stringBuilder) => _stringBuilder = stringBuilder;
64+
65+
public bool MoveNext()
66+
{
67+
_index++;
68+
return _index < _stringBuilder.Length;
69+
}
70+
71+
/// <summary>
72+
/// This is potentially dangerous as this is not always O(1)
73+
/// The StringBuilder has chunks, which are at most 8000 bytes long
74+
/// So having a string longer than 8000 bytes might have to access
75+
/// the second chunk.
76+
/// </summary>
77+
public char Current => _stringBuilder[_index];
78+
79+
public void Dispose() => _index = -1;
80+
}

EnumerateStringBuilder/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# How to enumerate through a StringBuilder
2+
3+
Did you ever wonder how we can iterate through a `StringBuilder`? I mean, of course, we can just call `ToString` and use the returned string, but that means we materialize the whole thing without good reason.
4+
5+
We can also use a normal for-loop. But we can also find a completely different and probably dumber way! And if you wonder: No, this is not something you do in your daily life, but by doing that thing, I can show some cool stuff C# and .NET offer.
6+
7+
Found [here](https://steven-giesel.com/blogPost/5161909a-a223-4312-b939-996a0f97bf75)

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Contains all of my examples from various blog posts. You can find a comprehensiv
44

55
| BlogPost | Publish Date |
66
| ---------------------------------------------------------------------------------------- | ------------ |
7+
| [How to enumerate through a StringBuilder](EnumerateStringBuilder/) | 03.12.2022 |
78
| [Frozen collections in .NET 8](FrozenSetBenchmark/) | 24.11.2022 |
89
| ["Use always a StringBuilder" - Internet myths](StringBuilderPerformance/) | 20.11.2022 |
910
| [Anonymous test data with AutoFixture](AutoFixtureXUnit/) | 19.11.2022 |

0 commit comments

Comments
 (0)