Skip to content

Commit e07a8e7

Browse files
committed
Use ISpanFormattable instead of concrete value types
1 parent ecedb31 commit e07a8e7

File tree

5 files changed

+65
-364
lines changed

5 files changed

+65
-364
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ All notable changes to **ValueStringBuilder** will be documented in this file. T
1010

1111
- implicit cast operator from `string` and `ReadOnlySpan<char>` to the `ValueStringBuilder` with pre-initialized buffer
1212

13+
### Removed
14+
15+
- Removed value type overloads for `Append` and `Insert` and just offer `Append(ISpanFormattable)` and `Insert(ISpanFormattable)`, which covers more cases.
16+
1317
## [1.7.0] - 2022-11-12
1418

1519
### Added

src/LinkDotNet.StringBuilder/ValueStringBuilder.Append.cs

Lines changed: 11 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,82 +14,13 @@ public ref partial struct ValueStringBuilder
1414
/// <summary>
1515
/// Appends the string representation of the character to the builder.
1616
/// </summary>
17-
/// <param name="value">Integer to add.</param>
17+
/// <param name="value">Formattable span to add.</param>
1818
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
19+
/// <param name="bufferSize">Size of the buffer allocated on the stack.</param>
20+
/// <typeparam name="T">Any <see cref="ISpanFormattable"/>.</typeparam>
1921
[MethodImpl(MethodImplOptions.AggressiveInlining)]
20-
public void Append(char value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
21-
22-
/// <summary>
23-
/// Appends the string representation of the signed byte to the builder.
24-
/// </summary>
25-
/// <param name="value">Signed byte to add.</param>
26-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
27-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28-
public void Append(sbyte value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
29-
30-
/// <summary>
31-
/// Appends the string representation of the byte to the builder.
32-
/// </summary>
33-
/// <param name="value">Byte to add.</param>
34-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
35-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
36-
public void Append(byte value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
37-
38-
/// <summary>
39-
/// Appends the string representation of the short to the builder.
40-
/// </summary>
41-
/// <param name="value">Short to add.</param>
42-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
43-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44-
public void Append(short value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
45-
46-
/// <summary>
47-
/// Appends the string representation of the integer to the builder.
48-
/// </summary>
49-
/// <param name="value">Integer to add.</param>
50-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
51-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
52-
public void Append(int value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
53-
54-
/// <summary>
55-
/// Appends the string representation of the long integer to the builder.
56-
/// </summary>
57-
/// <param name="value">Long integer to add.</param>
58-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
59-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
60-
public void Append(long value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
61-
62-
/// <summary>
63-
/// Appends the string representation of the float to the builder.
64-
/// </summary>
65-
/// <param name="value">Float to add.</param>
66-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
67-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
68-
public void Append(float value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
69-
70-
/// <summary>
71-
/// Appends the string representation of the double to the builder.
72-
/// </summary>
73-
/// <param name="value">Double to add.</param>
74-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
75-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76-
public void Append(double value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
77-
78-
/// <summary>
79-
/// Appends the string representation of the decimal to the builder.
80-
/// </summary>
81-
/// <param name="value">Decimal to add.</param>
82-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
83-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
84-
public void Append(decimal value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
85-
86-
/// <summary>
87-
/// Appends the string representation of the Guid to the builder.
88-
/// </summary>
89-
/// <param name="value">Guid to add.</param>
90-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
91-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
92-
public void Append(Guid value, ReadOnlySpan<char> format = default) => AppendSpanFormattable(value, format);
22+
public void Append<T>(T value, ReadOnlySpan<char> format = default, int bufferSize = 36)
23+
where T : ISpanFormattable => AppendSpanFormattable(value, format, bufferSize);
9324

9425
/// <summary>
9526
/// Appends a string to the string builder.
@@ -129,10 +60,10 @@ public void AppendLine(scoped ReadOnlySpan<char> str)
12960
}
13061

13162
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132-
private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = default)
63+
private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = default, int bufferSize = 36)
13364
where T : ISpanFormattable
13465
{
135-
Span<char> tempBuffer = stackalloc char[36];
66+
Span<char> tempBuffer = stackalloc char[bufferSize];
13667
if (value.TryFormat(tempBuffer, out var written, format, null))
13768
{
13869
var newSize = written + bufferPosition;
@@ -144,5 +75,9 @@ private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = defau
14475
tempBuffer[..written].CopyTo(buffer[bufferPosition..]);
14576
bufferPosition = newSize;
14677
}
78+
else
79+
{
80+
throw new InvalidOperationException($"Could not insert {value} into given buffer. Is the buffer large enough?");
81+
}
14782
}
14883
}

src/LinkDotNet.StringBuilder/ValueStringBuilder.Insert.cs

Lines changed: 11 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -16,91 +16,13 @@ public ref partial struct ValueStringBuilder
1616
/// Insert the string representation of the char to the builder at the given index.
1717
/// </summary>
1818
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
19-
/// <param name="value">Character to insert into this builder.</param>
19+
/// <param name="value">Formattable span to insert into this builder.</param>
2020
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
21+
/// <param name="bufferSize">Size of the buffer allocated on the stack.</param>
22+
/// <typeparam name="T">Any <see cref="ISpanFormattable"/>.</typeparam>
2123
[MethodImpl(MethodImplOptions.AggressiveInlining)]
22-
public void Insert(int index, char value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
23-
24-
/// <summary>
25-
/// Insert the string representation of the signed byte to the builder at the given index.
26-
/// </summary>
27-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
28-
/// <param name="value">Signed byte to insert into this builder.</param>
29-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
30-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
31-
public void Insert(int index, sbyte value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
32-
33-
/// <summary>
34-
/// Insert the string representation of the byte to the builder at the given index.
35-
/// </summary>
36-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
37-
/// <param name="value">Byte to insert into this builder.</param>
38-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
39-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
40-
public void Insert(int index, byte value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
41-
42-
/// <summary>
43-
/// Insert the string representation of the short to the builder at the given index.
44-
/// </summary>
45-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
46-
/// <param name="value">Short to insert into this builder.</param>
47-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
48-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49-
public void Insert(int index, short value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
50-
51-
/// <summary>
52-
/// Insert the string representation of the integer to the builder at the given index.
53-
/// </summary>
54-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
55-
/// <param name="value">Integer to insert into this builder.</param>
56-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
57-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
58-
public void Insert(int index, int value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
59-
60-
/// <summary>
61-
/// Insert the string representation of the long to the builder at the given index.
62-
/// </summary>
63-
/// <param name="index">Long where <paramref name="value"/> should be inserted.</param>
64-
/// <param name="value">String to insert into this builder.</param>
65-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
66-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
67-
public void Insert(int index, long value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
68-
69-
/// <summary>
70-
/// Insert the string representation of the float to the builder at the given index.
71-
/// </summary>
72-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
73-
/// <param name="value">Float to insert into this builder.</param>
74-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
75-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76-
public void Insert(int index, float value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
77-
78-
/// <summary>
79-
/// Insert the string representation of the double to the builder at the given index.
80-
/// </summary>
81-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
82-
/// <param name="value">Double to insert into this builder.</param>
83-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
84-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
85-
public void Insert(int index, double value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
86-
87-
/// <summary>
88-
/// Insert the string representation of the decimal to the builder at the given index.
89-
/// </summary>
90-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
91-
/// <param name="value">Decimal to insert into this builder.</param>
92-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
93-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
94-
public void Insert(int index, decimal value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
95-
96-
/// <summary>
97-
/// Insert the string representation of the Guid to the builder at the given index.
98-
/// </summary>
99-
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
100-
/// <param name="value">Guid to insert into this builder.</param>
101-
/// <param name="format">Optional formatter. If not provided the default of the given instance is taken.</param>
102-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
103-
public void Insert(int index, Guid value, ReadOnlySpan<char> format = default) => InsertSpanFormattable(index, value, format);
24+
public void Insert<T>(int index, T value, ReadOnlySpan<char> format = default, int bufferSize = 36)
25+
where T : ISpanFormattable => InsertSpanFormattable(index, value, format, bufferSize);
10426

10527
/// <summary>
10628
/// Appends the string representation of the boolean to the builder.
@@ -136,7 +58,7 @@ public void Insert(int index, scoped ReadOnlySpan<char> value)
13658
}
13759

13860
[MethodImpl(MethodImplOptions.AggressiveInlining)]
139-
private void InsertSpanFormattable<T>(int index, T value, ReadOnlySpan<char> format = default)
61+
private void InsertSpanFormattable<T>(int index, T value, ReadOnlySpan<char> format, int bufferSize)
14062
where T : ISpanFormattable
14163
{
14264
if (index < 0)
@@ -149,7 +71,7 @@ private void InsertSpanFormattable<T>(int index, T value, ReadOnlySpan<char> for
14971
throw new ArgumentOutOfRangeException(nameof(index), "The given index can't be bigger than the string itself.");
15072
}
15173

152-
Span<char> tempBuffer = stackalloc char[36];
74+
Span<char> tempBuffer = stackalloc char[bufferSize];
15375
if (value.TryFormat(tempBuffer, out var written, format, null))
15476
{
15577
bufferPosition += written;
@@ -166,5 +88,9 @@ private void InsertSpanFormattable<T>(int index, T value, ReadOnlySpan<char> for
16688
// Add new word
16789
tempBuffer[..written].CopyTo(buffer[index..shift]);
16890
}
91+
else
92+
{
93+
throw new InvalidOperationException($"Could not insert {value} into given buffer. Is the buffer large enough?");
94+
}
16995
}
17096
}

tests/LinkDotNet.StringBuilder.UnitTests/ValueStringBuilder.Append.Tests.cs

Lines changed: 19 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void ShouldGetIndexIfGiven()
6868
}
6969

7070
[Fact]
71-
public void ShouldAppendFloat()
71+
public void ShouldAppendSpanFormattable()
7272
{
7373
using var builder = new ValueStringBuilder();
7474

@@ -77,106 +77,6 @@ public void ShouldAppendFloat()
7777
builder.ToString().Should().Be("2.2");
7878
}
7979

80-
[Fact]
81-
public void ShouldAppendDouble()
82-
{
83-
using var builder = new ValueStringBuilder();
84-
85-
builder.Append(2.2d);
86-
87-
builder.ToString().Should().Be("2.2");
88-
}
89-
90-
[Fact]
91-
public void ShouldAppendDecimal()
92-
{
93-
using var builder = new ValueStringBuilder();
94-
95-
builder.Append(2.2m);
96-
97-
builder.ToString().Should().Be("2.2");
98-
}
99-
100-
[Fact]
101-
public void ShouldAppendInteger()
102-
{
103-
using var builder = new ValueStringBuilder();
104-
105-
builder.Append(-2);
106-
107-
builder.ToString().Should().Be("-2");
108-
}
109-
110-
[Fact]
111-
public void ShouldAppendUnsignedInteger()
112-
{
113-
using var builder = new ValueStringBuilder();
114-
115-
builder.Append(2U);
116-
117-
builder.ToString().Should().Be("2");
118-
}
119-
120-
[Fact]
121-
public void ShouldAppendLong()
122-
{
123-
using var builder = new ValueStringBuilder();
124-
125-
builder.Append(2L);
126-
127-
builder.ToString().Should().Be("2");
128-
}
129-
130-
[Fact]
131-
public void ShouldAppendChar()
132-
{
133-
using var builder = new ValueStringBuilder();
134-
135-
builder.Append('2');
136-
137-
builder.ToString().Should().Be("2");
138-
}
139-
140-
[Fact]
141-
public void ShouldAppendShort()
142-
{
143-
using var builder = new ValueStringBuilder();
144-
145-
builder.Append((short)2);
146-
147-
builder.ToString().Should().Be("2");
148-
}
149-
150-
[Fact]
151-
public void ShouldAppendBool()
152-
{
153-
using var builder = new ValueStringBuilder();
154-
155-
builder.Append(true);
156-
157-
builder.ToString().Should().Be("True");
158-
}
159-
160-
[Fact]
161-
public void ShouldAppendByte()
162-
{
163-
using var builder = new ValueStringBuilder();
164-
165-
builder.Append((byte)2);
166-
167-
builder.ToString().Should().Be("2");
168-
}
169-
170-
[Fact]
171-
public void ShouldAppendSignedByte()
172-
{
173-
using var builder = new ValueStringBuilder();
174-
175-
builder.Append((sbyte)2);
176-
177-
builder.ToString().Should().Be("2");
178-
}
179-
18080
[Fact]
18181
public void ShouldAppendMultipleChars()
18282
{
@@ -211,4 +111,22 @@ public void ShouldAppendGuid()
211111

212112
builder.ToString().Should().Be("00000000-0000-0000-0000-000000000000");
213113
}
114+
115+
[Fact]
116+
public void ShouldThrowIfNotAppendable()
117+
{
118+
using var builder = new ValueStringBuilder();
119+
120+
try
121+
{
122+
builder.Insert(0, Guid.Empty, bufferSize: 1);
123+
}
124+
catch (InvalidOperationException)
125+
{
126+
Assert.True(true);
127+
return;
128+
}
129+
130+
Assert.False(true);
131+
}
214132
}

0 commit comments

Comments
 (0)