Skip to content

Commit 449f64d

Browse files
authored
Add InplaceStringBuilder (#157)
1 parent ff65f89 commit 449f64d

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace Microsoft.Extensions.Primitives
9+
{
10+
[DebuggerDisplay("Value = {_value}")]
11+
public struct InplaceStringBuilder
12+
{
13+
private int _capacity;
14+
private int _offset;
15+
private bool _writing;
16+
private string _value;
17+
18+
public InplaceStringBuilder(int capacity) : this()
19+
{
20+
_capacity = capacity;
21+
}
22+
23+
public int Capacity
24+
{
25+
get { return _capacity; }
26+
set
27+
{
28+
if (value < 0)
29+
{
30+
throw new ArgumentOutOfRangeException(nameof(value));
31+
}
32+
if (_writing)
33+
{
34+
throw new InvalidOperationException("Cannot change capacity after write started.");
35+
}
36+
_capacity = value;
37+
}
38+
}
39+
40+
public unsafe void Append(string s)
41+
{
42+
EnsureCapacity(s.Length);
43+
fixed (char* destination = _value)
44+
fixed (char* source = s)
45+
{
46+
//TODO: https://github.com/aspnet/Common/issues/158
47+
Unsafe.CopyBlock(destination + _offset, source, (uint)s.Length * 2);
48+
_offset += s.Length;
49+
}
50+
}
51+
public unsafe void Append(char c)
52+
{
53+
EnsureCapacity(1);
54+
fixed (char* destination = _value)
55+
{
56+
destination[_offset++] = c;
57+
}
58+
}
59+
60+
private void EnsureCapacity(int length)
61+
{
62+
if (_value == null)
63+
{
64+
_writing = true;
65+
_value = new string('\0', _capacity);
66+
}
67+
if (_offset + length > _capacity)
68+
{
69+
throw new InvalidOperationException($"Not enough capacity to write '{length}' characters, only '{_capacity - _offset}' left.");
70+
}
71+
}
72+
73+
public override string ToString()
74+
{
75+
if (_offset != _capacity)
76+
{
77+
throw new InvalidOperationException($"Entire reserved capacity was not used. Capacity: '{_capacity}', written '{_offset}'.");
78+
}
79+
return _value;
80+
}
81+
}
82+
}

src/Microsoft.Extensions.Primitives/project.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
"nowarn": [
1717
"CS1591"
1818
],
19-
"xmlDoc": true
19+
"xmlDoc": true,
20+
"allowUnsafe": true
2021
},
2122
"dependencies": {
2223
"Microsoft.Extensions.HashCodeCombiner.Sources": {
2324
"type": "build",
2425
"version": "1.1.0-*"
25-
}
26+
},
27+
"System.Diagnostics.Debug": "4.0.11",
28+
"System.Runtime.CompilerServices.Unsafe": "4.0.0"
2629
},
2730
"frameworks": {
2831
"netstandard1.0": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using Microsoft.Extensions.Primitives;
3+
using Xunit;
4+
5+
namespace Microsoft.AspNetCore.Http.Tests.Internal
6+
{
7+
public class InplaceStringBuilderTest
8+
{
9+
[Fact]
10+
public void ToString_ReturnsStringWithAllAppendedValues()
11+
{
12+
var s1 = "123";
13+
var c1 = '4';
14+
var s2 = "56789";
15+
16+
var formatter = new InplaceStringBuilder();
17+
formatter.Capacity += s1.Length + 1 + s2.Length;
18+
formatter.Append(s1);
19+
formatter.Append(c1);
20+
formatter.Append(s2);
21+
Assert.Equal("123456789", formatter.ToString());
22+
}
23+
24+
[Fact]
25+
public void Build_ThrowsIfNotEnoughWritten()
26+
{
27+
var formatter = new InplaceStringBuilder(5);
28+
formatter.Append("123");
29+
var exception = Assert.Throws<InvalidOperationException>(() => formatter.ToString());
30+
Assert.Equal(exception.Message, "Entire reserved capacity was not used. Capacity: '5', written '3'.");
31+
}
32+
33+
[Fact]
34+
public void Capacity_ThrowsIfAppendWasCalled()
35+
{
36+
var formatter = new InplaceStringBuilder(3);
37+
formatter.Append("123");
38+
39+
var exception = Assert.Throws<InvalidOperationException>(() => formatter.Capacity = 5);
40+
Assert.Equal(exception.Message, "Cannot change capacity after write started.");
41+
}
42+
43+
[Fact]
44+
public void Append_ThrowsIfNotEnoughSpace()
45+
{
46+
var formatter = new InplaceStringBuilder(1);
47+
48+
var exception = Assert.Throws<InvalidOperationException>(() => formatter.Append("123"));
49+
Assert.Equal(exception.Message, "Not enough capacity to write '3' characters, only '1' left.");
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)