Skip to content

Commit e04dee0

Browse files
authored
Avoid allocation in PathString.Add (#28855)
Also some other refactorings based on IDE's recommendations ``` | Method | Mean | Error | StdDev | Ratio | |---------- |---------:|---------:|---------:|------:| | Baseline | 18.56 ns | 0.099 ns | 0.088 ns | 1.00 | | Create | 12.70 ns | 0.174 ns | 0.154 ns | 0.68 | ```
1 parent eb7754a commit e04dee0

File tree

1 file changed

+15
-27
lines changed

1 file changed

+15
-27
lines changed

src/Http/Http.Abstractions/src/PathString.cs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Http
1919
/// <summary>
2020
/// Represents the empty path. This field is read-only.
2121
/// </summary>
22-
public static readonly PathString Empty = new PathString(string.Empty);
22+
public static readonly PathString Empty = new(string.Empty);
2323

2424
/// <summary>
2525
/// Initialize the path string with a given value. This value must be in unescaped format. Use
@@ -103,11 +103,7 @@ private static string ToEscapedUriComponent(string value, int i)
103103
if (requiresEscaping)
104104
{
105105
// the current segment requires escape
106-
if (buffer == null)
107-
{
108-
buffer = new StringBuilder(value.Length * 3);
109-
}
110-
106+
buffer ??= new StringBuilder(value.Length * 3);
111107
buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
112108

113109
requiresEscaping = false;
@@ -131,11 +127,7 @@ private static string ToEscapedUriComponent(string value, int i)
131127
if (!requiresEscaping)
132128
{
133129
// the current segment doesn't require escape
134-
if (buffer == null)
135-
{
136-
buffer = new StringBuilder(value.Length * 3);
137-
}
138-
130+
buffer ??= new StringBuilder(value.Length * 3);
139131
buffer.Append(value, start, count);
140132

141133
requiresEscaping = true;
@@ -156,10 +148,7 @@ private static string ToEscapedUriComponent(string value, int i)
156148
{
157149
if (count > 0)
158150
{
159-
if (buffer == null)
160-
{
161-
buffer = new StringBuilder(value.Length * 3);
162-
}
151+
buffer ??= new StringBuilder(value.Length * 3);
163152

164153
if (requiresEscaping)
165154
{
@@ -259,7 +248,7 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
259248
{
260249
if (value1.Length == value2.Length || value1[value2.Length] == '/')
261250
{
262-
remaining = new PathString(value1.Substring(value2.Length));
251+
remaining = new PathString(value1[value2.Length..]);
263252
return true;
264253
}
265254
}
@@ -298,7 +287,7 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
298287
if (value1.Length == value2.Length || value1[value2.Length] == '/')
299288
{
300289
matched = new PathString(value1.Substring(0, value2.Length));
301-
remaining = new PathString(value1.Substring(value2.Length));
290+
remaining = new PathString(value1[value2.Length..]);
302291
return true;
303292
}
304293
}
@@ -315,11 +304,12 @@ public PathString Add(PathString other)
315304
{
316305
if (HasValue &&
317306
other.HasValue &&
318-
Value![Value.Length - 1] == '/')
307+
Value[^1] == '/')
319308
{
320309
// If the path string has a trailing slash and the other string has a leading slash, we need
321310
// to trim one of them.
322-
return new PathString(Value + other.Value!.Substring(1));
311+
var combined = string.Concat(Value.AsSpan(), other.Value.AsSpan(1));
312+
return new PathString(combined);
323313
}
324314

325315
return new PathString(Value + other.Value);
@@ -366,11 +356,11 @@ public bool Equals(PathString other, StringComparison comparisonType)
366356
/// <returns>True if both PathString values are equal</returns>
367357
public override bool Equals(object? obj)
368358
{
369-
if (ReferenceEquals(null, obj))
359+
if (obj is null)
370360
{
371361
return !HasValue;
372362
}
373-
return obj is PathString && Equals((PathString)obj);
363+
return obj is PathString pathString && Equals(pathString);
374364
}
375365

376366
/// <summary>
@@ -471,18 +461,16 @@ internal static PathString ConvertFromString(string? s)
471461
internal sealed class PathStringConverter : TypeConverter
472462
{
473463
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
474-
=> sourceType == typeof(string)
475-
? true
476-
: base.CanConvertFrom(context, sourceType);
464+
=> sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
477465

478466
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
479-
=> value is string
480-
? PathString.ConvertFromString((string)value)
467+
=> value is string @string
468+
? PathString.ConvertFromString(@string)
481469
: base.ConvertFrom(context, culture, value);
482470

483471
public override object ConvertTo(ITypeDescriptorContext context,
484472
CultureInfo culture, object value, Type destinationType)
485-
=> destinationType == typeof(string)
473+
=> destinationType == typeof(string)
486474
? value.ToString() ?? string.Empty
487475
: base.ConvertTo(context, culture, value, destinationType);
488476
}

0 commit comments

Comments
 (0)