diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
index ee53b9e626c751..3eae142edcd4cb 100644
--- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
+++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
@@ -5,11 +5,28 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace System.Net.Http.Headers
{
+ ///
+ /// Key/value pairs of headers. The value is either a raw or a .
+ /// We're using a custom type instead of because we need ref access to fields.
+ ///
+ internal struct HeaderEntry
+ {
+ public HeaderDescriptor Key;
+ public object Value;
+
+ public HeaderEntry(HeaderDescriptor key, object value)
+ {
+ Key = key;
+ Value = value;
+ }
+ }
+
public abstract class HttpHeaders : IEnumerable>>
{
// This type is used to store a collection of headers in 'headerStore':
@@ -32,8 +49,9 @@ public abstract class HttpHeaders : IEnumerableKey/value pairs of headers. The value is either a raw or a .
- private Dictionary? _headerStore;
+ /// Either a array or a Dictionary<, >
+ private object? _headerStore;
+ private int _count;
private readonly HttpHeaderType _allowedHeaderTypes;
private readonly HttpHeaderType _treatAsCustomHeaderTypes;
@@ -52,8 +70,6 @@ internal HttpHeaders(HttpHeaderType allowedHeaderTypes, HttpHeaderType treatAsCu
_treatAsCustomHeaderTypes = treatAsCustomHeaderTypes & ~HttpHeaderType.NonTrailing;
}
- internal Dictionary? HeaderStore => _headerStore;
-
/// Gets a view of the contents of this headers collection that does not parse nor validate the data upon access.
public HttpHeadersNonValidated NonValidated => new HttpHeadersNonValidated(this);
@@ -71,7 +87,8 @@ internal void Add(HeaderDescriptor descriptor, string? value)
// it to the store if we added at least one value.
if (addToStore && (info.ParsedValue != null))
{
- AddHeaderToStore(descriptor, info);
+ Debug.Assert(!ContainsKey(descriptor));
+ AddEntryToStore(new HeaderEntry(descriptor, info));
}
}
@@ -104,7 +121,8 @@ internal void Add(HeaderDescriptor descriptor, IEnumerable values)
// However, if all values for a _new_ header were invalid, then don't add the header.
if (addToStore && (info.ParsedValue != null))
{
- AddHeaderToStore(descriptor, info);
+ Debug.Assert(!ContainsKey(descriptor));
+ AddEntryToStore(new HeaderEntry(descriptor, info));
}
}
}
@@ -120,29 +138,24 @@ internal bool TryAddWithoutValidation(HeaderDescriptor descriptor, string? value
// values, e.g. adding two null-strings (or empty, or whitespace-only) results in "My-Header: ,".
value ??= string.Empty;
- // Ensure the header store dictionary has been created.
- _headerStore ??= new Dictionary();
+ ref object? storeValueRef = ref GetValueRefOrAddDefault(descriptor);
+ object? currentValue = storeValueRef;
- if (_headerStore.TryGetValue(descriptor, out object? currentValue))
+ if (currentValue is null)
{
- if (currentValue is HeaderStoreItemInfo info)
- {
- // The header store already contained a HeaderStoreItemInfo, so add to it.
- AddRawValue(info, value);
- }
- else
+ storeValueRef = value;
+ }
+ else
+ {
+ if (currentValue is not HeaderStoreItemInfo info)
{
// The header store contained a single raw string value, so promote it
// to being a HeaderStoreItemInfo and add to it.
Debug.Assert(currentValue is string);
- _headerStore[descriptor] = info = new HeaderStoreItemInfo() { RawValue = currentValue };
- AddRawValue(info, value);
+ storeValueRef = info = new HeaderStoreItemInfo() { RawValue = currentValue };
}
- }
- else
- {
- // The header store did not contain the header. Add the raw string.
- _headerStore.Add(descriptor, value);
+
+ AddRawValue(info, value);
}
return true;
@@ -159,28 +172,33 @@ internal bool TryAddWithoutValidation(HeaderDescriptor descriptor, IEnumerable enumerator = values.GetEnumerator())
+ using IEnumerator enumerator = values.GetEnumerator();
+ if (enumerator.MoveNext())
{
+ TryAddWithoutValidation(descriptor, enumerator.Current);
if (enumerator.MoveNext())
{
- TryAddWithoutValidation(descriptor, enumerator.Current);
- if (enumerator.MoveNext())
+ ref object? storeValueRef = ref GetValueRefOrAddDefault(descriptor);
+ Debug.Assert(storeValueRef is not null);
+
+ object value = storeValueRef;
+ if (value is not HeaderStoreItemInfo info)
{
- HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor, parseRawValues: false);
- do
- {
- AddRawValue(info, enumerator.Current ?? string.Empty);
- }
- while (enumerator.MoveNext());
+ Debug.Assert(value is string);
+ storeValueRef = info = new HeaderStoreItemInfo { RawValue = value };
}
+
+ do
+ {
+ AddRawValue(info, enumerator.Current ?? string.Empty);
+ }
+ while (enumerator.MoveNext());
}
}
return true;
}
- public void Clear() => _headerStore?.Clear();
-
public IEnumerable GetValues(string name) => GetValues(GetHeaderDescriptor(name));
internal IEnumerable GetValues(HeaderDescriptor descriptor)
@@ -206,7 +224,7 @@ public bool TryGetValues(string name, [NotNullWhen(true)] out IEnumerable? values)
{
- if (_headerStore != null && TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
+ if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
{
values = GetStoreValuesAsStringArray(descriptor, info);
return true;
@@ -223,7 +241,7 @@ internal bool Contains(HeaderDescriptor descriptor)
// We can't just call headerStore.ContainsKey() since after parsing the value the header may not exist
// anymore (if the value contains newline chars, we remove the header). So try to parse the
// header value.
- return _headerStore != null && TryGetAndParseHeaderInfo(descriptor, out _);
+ return TryGetAndParseHeaderInfo(descriptor, out _);
}
public override string ToString()
@@ -235,35 +253,34 @@ public override string ToString()
var vsb = new ValueStringBuilder(stackalloc char[512]);
- if (_headerStore is Dictionary headerStore)
+ foreach (HeaderEntry entry in GetEntries())
{
- foreach (KeyValuePair header in headerStore)
- {
- vsb.Append(header.Key.Name);
- vsb.Append(": ");
+ vsb.Append(entry.Key.Name);
+ vsb.Append(": ");
- GetStoreValuesAsStringOrStringArray(header.Key, header.Value, out string? singleValue, out string[]? multiValue);
- Debug.Assert(singleValue is not null ^ multiValue is not null);
+ GetStoreValuesAsStringOrStringArray(entry.Key, entry.Value, out string? singleValue, out string[]? multiValue);
+ Debug.Assert(singleValue is not null ^ multiValue is not null);
- if (singleValue is not null)
- {
- vsb.Append(singleValue);
- }
- else
- {
- // Note that if we get multiple values for a header that doesn't support multiple values, we'll
- // just separate the values using a comma (default separator).
- string? separator = header.Key.Parser is HttpHeaderParser parser && parser.SupportsMultipleValues ? parser.Separator : HttpHeaderParser.DefaultSeparator;
+ if (singleValue is not null)
+ {
+ vsb.Append(singleValue);
+ }
+ else
+ {
+ // Note that if we get multiple values for a header that doesn't support multiple values, we'll
+ // just separate the values using a comma (default separator).
+ string? separator = entry.Key.Parser is HttpHeaderParser parser && parser.SupportsMultipleValues ? parser.Separator : HttpHeaderParser.DefaultSeparator;
- for (int i = 0; i < multiValue!.Length; i++)
- {
- if (i != 0) vsb.Append(separator);
- vsb.Append(multiValue[i]);
- }
+ Debug.Assert(multiValue is not null && multiValue.Length > 0);
+ vsb.Append(multiValue[0]);
+ for (int i = 1; i < multiValue.Length; i++)
+ {
+ vsb.Append(separator);
+ vsb.Append(multiValue[i]);
}
-
- vsb.Append(Environment.NewLine);
}
+
+ vsb.Append(Environment.NewLine);
}
return vsb.ToString();
@@ -292,39 +309,57 @@ internal string GetHeaderString(HeaderDescriptor descriptor)
#region IEnumerable>> Members
- public IEnumerator>> GetEnumerator() => _headerStore != null && _headerStore.Count > 0 ?
- GetEnumeratorCore() :
- ((IEnumerable>>)Array.Empty>>()).GetEnumerator();
+ public IEnumerator>> GetEnumerator() => _count == 0 ?
+ ((IEnumerable>>)Array.Empty>>()).GetEnumerator() :
+ GetEnumeratorCore();
private IEnumerator>> GetEnumeratorCore()
{
- foreach (KeyValuePair header in _headerStore!)
+ HeaderEntry[]? entries = GetEntriesArray();
+ Debug.Assert(_count != 0 && entries is not null, "Caller should have validated the collection is not empty");
+
+ int count = _count;
+ for (int i = 0; i < count; i++)
{
- HeaderDescriptor descriptor = header.Key;
- object value = header.Value;
+ HeaderEntry entry = entries[i];
- HeaderStoreItemInfo? info = value as HeaderStoreItemInfo;
- if (info is null)
+ if (entry.Value is not HeaderStoreItemInfo info)
{
// To retain consistent semantics, we need to upgrade a raw string to a HeaderStoreItemInfo
// during enumeration so that we can parse the raw value in order to a) return
// the correct set of parsed values, and b) update the instance for subsequent enumerations
// to reflect that parsing.
- _headerStore[descriptor] = info = new HeaderStoreItemInfo() { RawValue = value };
+ info = new HeaderStoreItemInfo() { RawValue = entry.Value };
+
+ if (EntriesAreLiveView)
+ {
+ entries[i].Value = info;
+ }
+ else
+ {
+ Debug.Assert(ContainsKey(entry.Key));
+ ((Dictionary)_headerStore!)[entry.Key] = info;
+ }
}
// Make sure we parse all raw values before returning the result. Note that this has to be
// done before we calculate the array length (next line): A raw value may contain a list of
// values.
- if (!ParseRawHeaderValues(descriptor, info, removeEmptyHeader: false))
+ if (!ParseRawHeaderValues(entry.Key, info))
{
- // We have an invalid header value (contains newline chars). Delete it.
- _headerStore.Remove(descriptor);
+ // We saw an invalid header value (contains newline chars) and deleted it.
+
+ // If the HeaderEntry[] we are enumerating is the live header store, the entries have shifted.
+ if (EntriesAreLiveView)
+ {
+ i--;
+ count--;
+ }
}
else
{
- string[] values = GetStoreValuesAsStringArray(descriptor, info);
- yield return new KeyValuePair>(descriptor.Name, values);
+ string[] values = GetStoreValuesAsStringArray(entry.Key, info);
+ yield return new KeyValuePair>(entry.Key.Name, values);
}
}
}
@@ -342,7 +377,7 @@ internal void AddParsedValue(HeaderDescriptor descriptor, object value)
Debug.Assert(value != null);
Debug.Assert(descriptor.Parser != null, "Can't add parsed value if there is no parser available.");
- HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor, parseRawValues: true);
+ HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor);
// If the current header has only one value, we can't add another value. The strongly typed property
// must not call AddParsedValue(), but SetParsedValue(). E.g. for headers like 'Date', 'Host'.
@@ -358,7 +393,7 @@ internal void SetParsedValue(HeaderDescriptor descriptor, object value)
// This method will first clear all values. This is used e.g. when setting the 'Date' or 'Host' header.
// i.e. headers not supporting collections.
- HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor, parseRawValues: true);
+ HeaderStoreItemInfo info = GetOrCreateHeaderInfo(descriptor);
info.InvalidValue = null;
info.ParsedValue = null;
@@ -381,17 +416,10 @@ internal void SetOrRemoveParsedValue(HeaderDescriptor descriptor, object? value)
public bool Remove(string name) => Remove(GetHeaderDescriptor(name));
- internal bool Remove(HeaderDescriptor descriptor) => _headerStore != null && _headerStore.Remove(descriptor);
-
internal bool RemoveParsedValue(HeaderDescriptor descriptor, object value)
{
Debug.Assert(value != null);
- if (_headerStore == null)
- {
- return false;
- }
-
// If we have a value for this header, then verify if we have a single value. If so, compare that
// value with 'item'. If we have a list of values, then remove 'item' from the list.
if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
@@ -462,11 +490,6 @@ internal bool ContainsParsedValue(HeaderDescriptor descriptor, object value)
{
Debug.Assert(value != null);
- if (_headerStore == null)
- {
- return false;
- }
-
// If we have a value for this header, then verify if we have a single value. If so, compare that
// value with 'item'. If we have a list of values, then compare each item in the list with 'item'.
if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
@@ -517,41 +540,58 @@ internal virtual void AddHeaders(HttpHeaders sourceHeaders)
Debug.Assert(sourceHeaders != null);
Debug.Assert(GetType() == sourceHeaders.GetType(), "Can only copy headers from an instance of the same type.");
- Dictionary? sourceHeadersStore = sourceHeaders._headerStore;
- if (sourceHeadersStore is null || sourceHeadersStore.Count == 0)
+ // Only add header values if they're not already set on the message. Note that we don't merge
+ // collections: If both the default headers and the message have set some values for a certain
+ // header, then we don't try to merge the values.
+ if (_count == 0 && sourceHeaders._headerStore is HeaderEntry[] sourceEntries)
{
- return;
- }
-
- _headerStore ??= new Dictionary();
+ // If the target collection is empty, we don't have to search for existing values
+ _count = sourceHeaders._count;
+ if (_headerStore is not HeaderEntry[] entries || entries.Length < _count)
+ {
+ entries = new HeaderEntry[sourceEntries.Length];
+ _headerStore = entries;
+ }
- foreach (KeyValuePair header in sourceHeadersStore)
- {
- // Only add header values if they're not already set on the message. Note that we don't merge
- // collections: If both the default headers and the message have set some values for a certain
- // header, then we don't try to merge the values.
- if (!_headerStore.ContainsKey(header.Key))
+ for (int i = 0; i < _count && i < sourceEntries.Length; i++)
{
- object sourceValue = header.Value;
- if (sourceValue is HeaderStoreItemInfo info)
+ HeaderEntry entry = sourceEntries[i];
+ if (entry.Value is HeaderStoreItemInfo info)
{
- AddHeaderInfo(header.Key, info);
+ entry.Value = CloneHeaderInfo(entry.Key, info);
}
- else
+ entries[i] = entry;
+ }
+ }
+ else
+ {
+ foreach (HeaderEntry entry in sourceHeaders.GetEntries())
+ {
+ ref object? storeValueRef = ref GetValueRefOrAddDefault(entry.Key);
+ if (storeValueRef is null)
{
- Debug.Assert(sourceValue is string);
- _headerStore.Add(header.Key, sourceValue);
+ object sourceValue = entry.Value;
+ if (sourceValue is HeaderStoreItemInfo info)
+ {
+ storeValueRef = CloneHeaderInfo(entry.Key, info);
+ }
+ else
+ {
+ Debug.Assert(sourceValue is string);
+ storeValueRef = sourceValue;
+ }
}
}
}
}
- private void AddHeaderInfo(HeaderDescriptor descriptor, HeaderStoreItemInfo sourceInfo)
+ private HeaderStoreItemInfo CloneHeaderInfo(HeaderDescriptor descriptor, HeaderStoreItemInfo sourceInfo)
{
- HeaderStoreItemInfo destinationInfo = CreateAndAddHeaderToStore(descriptor);
-
- // Always copy raw values
- destinationInfo.RawValue = CloneStringHeaderInfoValues(sourceInfo.RawValue);
+ var destinationInfo = new HeaderStoreItemInfo
+ {
+ // Always copy raw values
+ RawValue = CloneStringHeaderInfoValues(sourceInfo.RawValue)
+ };
if (descriptor.Parser == null)
{
@@ -585,6 +625,8 @@ private void AddHeaderInfo(HeaderDescriptor descriptor, HeaderStoreItemInfo sour
}
}
}
+
+ return destinationInfo;
}
private static void CloneAndAddValue(HeaderStoreItemInfo destinationInfo, object source)
@@ -623,74 +665,54 @@ private static void CloneAndAddValue(HeaderStoreItemInfo destinationInfo, object
}
}
- private HeaderStoreItemInfo GetOrCreateHeaderInfo(HeaderDescriptor descriptor, bool parseRawValues)
+ private HeaderStoreItemInfo GetOrCreateHeaderInfo(HeaderDescriptor descriptor)
{
- HeaderStoreItemInfo? result = null;
- bool found;
- if (parseRawValues)
+ if (TryGetAndParseHeaderInfo(descriptor, out HeaderStoreItemInfo? info))
{
- found = TryGetAndParseHeaderInfo(descriptor, out result);
+ return info;
}
else
{
- found = TryGetHeaderValue(descriptor, out object? value);
- if (found)
- {
- if (value is HeaderStoreItemInfo hsti)
- {
- result = hsti;
- }
- else
- {
- Debug.Assert(value is string);
- _headerStore![descriptor] = result = new HeaderStoreItemInfo { RawValue = value };
- }
- }
- }
-
- if (!found)
- {
- result = CreateAndAddHeaderToStore(descriptor);
+ return CreateAndAddHeaderToStore(descriptor);
}
-
- Debug.Assert(result != null);
- return result;
}
private HeaderStoreItemInfo CreateAndAddHeaderToStore(HeaderDescriptor descriptor)
{
+ Debug.Assert(!ContainsKey(descriptor));
+
// If we don't have the header in the store yet, add it now.
HeaderStoreItemInfo result = new HeaderStoreItemInfo();
// If the descriptor header type is in _treatAsCustomHeaderTypes, it must be converted to a custom header before calling this method
Debug.Assert((descriptor.HeaderType & _treatAsCustomHeaderTypes) == 0);
- AddHeaderToStore(descriptor, result);
+ AddEntryToStore(new HeaderEntry(descriptor, result));
return result;
}
- private void AddHeaderToStore(HeaderDescriptor descriptor, object value)
- {
- Debug.Assert(value is string || value is HeaderStoreItemInfo);
- (_headerStore ??= new Dictionary()).Add(descriptor, value);
- }
-
internal bool TryGetHeaderValue(HeaderDescriptor descriptor, [NotNullWhen(true)] out object? value)
{
- if (_headerStore == null)
+ ref object storeValueRef = ref GetValueRefOrNullRef(descriptor);
+ if (Unsafe.IsNullRef(ref storeValueRef))
{
value = null;
return false;
}
-
- return _headerStore.TryGetValue(descriptor, out value);
+ else
+ {
+ value = storeValueRef;
+ return true;
+ }
}
private bool TryGetAndParseHeaderInfo(HeaderDescriptor key, [NotNullWhen(true)] out HeaderStoreItemInfo? info)
{
- if (TryGetHeaderValue(key, out object? value))
+ ref object storeValueRef = ref GetValueRefOrNullRef(key);
+ if (!Unsafe.IsNullRef(ref storeValueRef))
{
+ object value = storeValueRef;
if (value is HeaderStoreItemInfo hsi)
{
info = hsi;
@@ -698,20 +720,21 @@ private bool TryGetAndParseHeaderInfo(HeaderDescriptor key, [NotNullWhen(true)]
else
{
Debug.Assert(value is string);
- _headerStore![key] = info = new HeaderStoreItemInfo() { RawValue = value };
+ storeValueRef = info = new HeaderStoreItemInfo() { RawValue = value };
}
- return ParseRawHeaderValues(key, info, removeEmptyHeader: true);
+ return ParseRawHeaderValues(key, info);
}
info = null;
return false;
}
- private bool ParseRawHeaderValues(HeaderDescriptor descriptor, HeaderStoreItemInfo info, bool removeEmptyHeader)
+ private bool ParseRawHeaderValues(HeaderDescriptor descriptor, HeaderStoreItemInfo info)
{
// Unlike TryGetHeaderInfo() this method tries to parse all non-validated header values (if any)
// before returning to the caller.
+ Debug.Assert(!info.IsEmpty);
if (info.RawValue != null)
{
List? rawValues = info.RawValue as List;
@@ -730,16 +753,12 @@ private bool ParseRawHeaderValues(HeaderDescriptor descriptor, HeaderStoreItemIn
info.RawValue = null;
// During parsing, we removed the value since it contains newline chars. Return false to indicate that
- // this is an empty header. If the caller specified to remove empty headers, we'll remove the header before
- // returning.
+ // this is an empty header.
if ((info.InvalidValue == null) && (info.ParsedValue == null))
{
- if (removeEmptyHeader)
- {
- // After parsing the raw value, no value is left because all values contain newline chars.
- Debug.Assert(_headerStore != null);
- _headerStore.Remove(descriptor);
- }
+ // After parsing the raw value, no value is left because all values contain newline chars.
+ Debug.Assert(_count > 0);
+ Remove(descriptor);
return false;
}
}
@@ -808,7 +827,8 @@ internal bool TryParseAndAddValue(HeaderDescriptor descriptor, string? value)
{
// If we get here, then the value could be parsed correctly. If we created a new HeaderStoreItemInfo, add
// it to the store if we added at least one value.
- AddHeaderToStore(descriptor, info);
+ Debug.Assert(!ContainsKey(descriptor));
+ AddEntryToStore(new HeaderEntry(descriptor, info));
}
return result;
@@ -1061,12 +1081,14 @@ internal bool TryGetHeaderDescriptor(string name, out HeaderDescriptor descripto
if (HeaderDescriptor.TryGet(name, out descriptor))
{
- if ((descriptor.HeaderType & _allowedHeaderTypes) != 0)
+ HttpHeaderType headerType = descriptor.HeaderType;
+
+ if ((headerType & _allowedHeaderTypes) != 0)
{
return true;
}
- if ((descriptor.HeaderType & _treatAsCustomHeaderTypes) != 0)
+ if ((headerType & _treatAsCustomHeaderTypes) != 0)
{
descriptor = descriptor.AsCustomHeader();
return true;
@@ -1128,7 +1150,8 @@ internal static void GetStoreValuesAsStringOrStringArray(HeaderDescriptor descri
}
else
{
- values = multiValue = length != 0 ? new string[length] : Array.Empty();
+ Debug.Assert(length > 1, "The header should have been removed when it became empty");
+ values = multiValue = new string[length];
}
int currentIndex = 0;
@@ -1252,5 +1275,234 @@ internal bool CanAddParsedValue(HttpHeaderParser parser)
internal bool IsEmpty => (RawValue == null) && (InvalidValue == null) && (ParsedValue == null);
}
+
+
+ #region Low-level implementation details that work with _headerStore directly
+
+ // Used to store the CollectionsMarshal.GetValueRefOrAddDefault out parameter.
+ // This is a workaround for the Roslyn bug where we can't use a discard instead:
+ // https://github.com/dotnet/roslyn/issues/56587#issuecomment-934955526
+ private static bool s_dictionaryGetValueRefOrAddDefaultExistsDummy;
+
+ private const int InitialCapacity = 4;
+ internal const int ArrayThreshold = 64; // Above this threshold, header ordering will not be preserved
+
+ internal HeaderEntry[]? GetEntriesArray()
+ {
+ object? store = _headerStore;
+ if (store is null)
+ {
+ return null;
+ }
+ else if (store is HeaderEntry[] entries)
+ {
+ return entries;
+ }
+ else
+ {
+ return GetEntriesFromDictionary();
+ }
+
+ HeaderEntry[] GetEntriesFromDictionary()
+ {
+ var dictionary = (Dictionary)_headerStore!;
+ var entries = new HeaderEntry[dictionary.Count];
+ int i = 0;
+ foreach (KeyValuePair entry in dictionary)
+ {
+ entries[i++] = new HeaderEntry
+ {
+ Key = entry.Key,
+ Value = entry.Value
+ };
+ }
+ return entries;
+ }
+ }
+
+ internal ReadOnlySpan GetEntries()
+ {
+ return new ReadOnlySpan(GetEntriesArray(), 0, _count);
+ }
+
+ internal int Count => _count;
+
+ private bool EntriesAreLiveView => _headerStore is HeaderEntry[];
+
+ private ref object GetValueRefOrNullRef(HeaderDescriptor key)
+ {
+ ref object valueRef = ref Unsafe.NullRef