Skip to content

Commit 7ffb9a4

Browse files
authored
TryAddWithoutValidation for multiple values could be more efficient (#102845)
1 parent a848547 commit 7ffb9a4

File tree

2 files changed

+104
-13
lines changed

2 files changed

+104
-13
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,27 +169,58 @@ internal bool TryAddWithoutValidation(HeaderDescriptor descriptor, IEnumerable<s
169169
{
170170
ArgumentNullException.ThrowIfNull(values);
171171

172-
using IEnumerator<string?> enumerator = values.GetEnumerator();
173-
if (enumerator.MoveNext())
172+
if (values is IList<string?> valuesList)
174173
{
175-
TryAddWithoutValidation(descriptor, enumerator.Current);
176-
if (enumerator.MoveNext())
174+
int count = valuesList.Count;
175+
176+
if (count > 0)
177177
{
178+
// The store value is either a string (a single unparsed value) or a HeaderStoreItemInfo.
179+
// The RawValue on HeaderStoreItemInfo can likewise be either a single string or a List<string>.
180+
178181
ref object? storeValueRef = ref GetValueRefOrAddDefault(descriptor);
179-
Debug.Assert(storeValueRef is not null);
182+
object? storeValue = storeValueRef;
180183

181-
object value = storeValueRef;
182-
if (value is not HeaderStoreItemInfo info)
184+
// If the storeValue was already set or we're adding more than 1 value,
185+
// we'll have to store the values in a List<string> on HeaderStoreItemInfo.
186+
if (storeValue is not null || count > 1)
183187
{
184-
Debug.Assert(value is string);
185-
storeValueRef = info = new HeaderStoreItemInfo { RawValue = value };
186-
}
188+
if (storeValue is not HeaderStoreItemInfo info)
189+
{
190+
storeValueRef = info = new HeaderStoreItemInfo { RawValue = storeValue };
191+
}
192+
193+
object? rawValue = info.RawValue;
194+
if (rawValue is not List<string> rawValues)
195+
{
196+
info.RawValue = rawValues = new List<string>();
197+
198+
if (rawValue != null)
199+
{
200+
rawValues.EnsureCapacity(count + 1);
201+
rawValues.Add((string)rawValue);
202+
}
203+
}
187204

188-
do
205+
rawValues.EnsureCapacity(rawValues.Count + count);
206+
207+
for (int i = 0; i < count; i++)
208+
{
209+
rawValues.Add(valuesList[i] ?? string.Empty);
210+
}
211+
}
212+
else
189213
{
190-
AddRawValue(info, enumerator.Current ?? string.Empty);
214+
// We're adding a single value to a new header entry. We can store the unparsed value as-is.
215+
storeValueRef = valuesList[0] ?? string.Empty;
191216
}
192-
while (enumerator.MoveNext());
217+
}
218+
}
219+
else
220+
{
221+
foreach (string? value in values)
222+
{
223+
TryAddWithoutValidation(descriptor, value ?? string.Empty);
193224
}
194225
}
195226

src/libraries/System.Net.Http/tests/UnitTests/Headers/HttpHeadersTest.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,6 +2532,66 @@ public void TryGetValues_InvalidValuesContainingNewLines_ShouldNotRemoveInvalidV
25322532
Assert.Equal(value, values.Single());
25332533
}
25342534

2535+
[Fact]
2536+
public void TryAddWithoutValidation_OneValidValueHeader_UseSpecialListImplementation()
2537+
{
2538+
const string Name = "customHeader1";
2539+
const string Value = "Value1";
2540+
2541+
var response = new HttpResponseMessage();
2542+
Assert.True(response.Headers.TryAddWithoutValidation(Name, new List<string> { Value }));
2543+
2544+
Assert.True(response.Headers.Contains(Name));
2545+
2546+
Assert.True(response.Headers.TryGetValues(Name, out IEnumerable<string> values));
2547+
Assert.Equal(Value, values.Single());
2548+
}
2549+
2550+
[Fact]
2551+
public void TryAddWithoutValidation_ThreeValidValueHeader_UseSpecialListImplementation()
2552+
{
2553+
const string Name = "customHeader1";
2554+
List<string> expectedValues = [ "Value1", "Value2", "Value3" ];
2555+
2556+
var response = new HttpResponseMessage();
2557+
Assert.True(response.Headers.TryAddWithoutValidation(Name, expectedValues));
2558+
2559+
Assert.True(response.Headers.Contains(Name));
2560+
2561+
Assert.True(response.Headers.TryGetValues(Name, out IEnumerable<string> values));
2562+
Assert.True(expectedValues.SequenceEqual(values));
2563+
}
2564+
2565+
[Fact]
2566+
public void TryAddWithoutValidation_OneValidValueHeader_UseGenericImplementation()
2567+
{
2568+
const string Name = "customHeader1";
2569+
const string Value = "Value1";
2570+
2571+
var response = new HttpResponseMessage();
2572+
Assert.True(response.Headers.TryAddWithoutValidation(Name, new HashSet<string> { Value }));
2573+
2574+
Assert.True(response.Headers.Contains(Name));
2575+
2576+
Assert.True(response.Headers.TryGetValues(Name, out IEnumerable<string> values));
2577+
Assert.Equal(Value, values.Single());
2578+
}
2579+
2580+
[Fact]
2581+
public void TryAddWithoutValidation_ThreeValidValueHeader_UseGenericImplementation()
2582+
{
2583+
const string Name = "customHeader1";
2584+
List<string> expectedValues = ["Value1", "Value2", "Value3"];
2585+
2586+
var response = new HttpResponseMessage();
2587+
Assert.True(response.Headers.TryAddWithoutValidation(Name, new HashSet<string>(expectedValues)));
2588+
2589+
Assert.True(response.Headers.Contains(Name));
2590+
2591+
Assert.True(response.Headers.TryGetValues(Name, out IEnumerable<string> values));
2592+
Assert.True(expectedValues.SequenceEqual(values));
2593+
}
2594+
25352595
public static IEnumerable<object[]> NumberOfHeadersUpToArrayThreshold_AddNonValidated_EnumerateNonValidated()
25362596
{
25372597
for (int i = 0; i <= HttpHeaders.ArrayThreshold; i++)

0 commit comments

Comments
 (0)