Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,7 @@ public bool NoCache
set { _noCache = value; }
}

public ICollection<string> NoCacheHeaders
{
get
{
if (_noCacheHeaders == null)
{
_noCacheHeaders = new ObjectCollection<string>(s_checkIsValidToken);
}
return _noCacheHeaders;
}
}
public ICollection<string> NoCacheHeaders => _noCacheHeaders ??= new ObjectCollection<string>(s_checkIsValidToken);

public bool NoStore
{
Expand Down Expand Up @@ -124,17 +114,7 @@ public bool Private
set { _privateField = value; }
}

public ICollection<string> PrivateHeaders
{
get
{
if (_privateHeaders == null)
{
_privateHeaders = new ObjectCollection<string>(s_checkIsValidToken);
}
return _privateHeaders;
}
}
public ICollection<string> PrivateHeaders => _privateHeaders ??= new ObjectCollection<string>(s_checkIsValidToken);

public bool MustRevalidate
{
Expand All @@ -148,17 +128,7 @@ public bool ProxyRevalidate
set { _proxyRevalidate = value; }
}

public ICollection<NameValueHeaderValue> Extensions
{
get
{
if (_extensions == null)
{
_extensions = new ObjectCollection<NameValueHeaderValue>();
}
return _extensions;
}
}
public ICollection<NameValueHeaderValue> Extensions => _extensions ??= new ObjectCollection<NameValueHeaderValue>();

public CacheControlHeaderValue()
{
Expand Down Expand Up @@ -604,11 +574,7 @@ private static bool TrySetOptionalTokenList(NameValueHeaderValue nameValue, ref
return false;
}

if (destination == null)
{
destination = new ObjectCollection<string>(s_checkIsValidToken);
}

destination ??= new ObjectCollection<string>(s_checkIsValidToken);
destination.Add(valueString.Substring(current, tokenLength));

current = current + tokenLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,7 @@ public string DispositionType
}
}

public ICollection<NameValueHeaderValue> Parameters
{
get
{
if (_parameters == null)
{
_parameters = new ObjectCollection<NameValueHeaderValue>();
}
return _parameters;
}
}
public ICollection<NameValueHeaderValue> Parameters => _parameters ??= new ObjectCollection<NameValueHeaderValue>();

public string? Name
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,7 @@ public string? CharSet
}
}

public ICollection<NameValueHeaderValue> Parameters
{
get
{
if (_parameters == null)
{
_parameters = new ObjectCollection<NameValueHeaderValue>();
}
return _parameters;
}
}
public ICollection<NameValueHeaderValue> Parameters => _parameters ??= new ObjectCollection<NameValueHeaderValue>();

[DisallowNull]
public string? MediaType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,7 @@ public class NameValueWithParametersHeaderValue : NameValueHeaderValue, ICloneab

private ObjectCollection<NameValueHeaderValue>? _parameters;

public ICollection<NameValueHeaderValue> Parameters
{
get
{
if (_parameters == null)
{
_parameters = new ObjectCollection<NameValueHeaderValue>();
}
return _parameters;
}
}
public ICollection<NameValueHeaderValue> Parameters => _parameters ??= new ObjectCollection<NameValueHeaderValue>();

public NameValueWithParametersHeaderValue(string name)
: base(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,212 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace System.Net.Http.Headers
{
// We need to prevent 'null' values in the collection. Since List<T> allows them, we will create
// a custom collection class. It is less efficient than List<T> but only used for small collections.
internal sealed class ObjectCollection<T> : Collection<T> where T : class
/// <summary>An <see cref="ICollection{T}"/> list that prohibits null elements and that is optimized for a small number of elements.</summary>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(nameof(DebugView))]
internal sealed class ObjectCollection<T> : ICollection<T> where T : class
{
private static readonly Action<T> s_defaultValidator = CheckNotNull;
private const int DefaultSize = 4;

private readonly Action<T> _validator;
/// <summary>Optional delegate used to validate added items.</summary>
private readonly Action<T>? _validator;
/// <summary>null, a T, or a T[].</summary>
internal object? _items;
/// <summary>Number of elements stored in the collection.</summary>
internal int _size;

public ObjectCollection()
: this(s_defaultValidator)
public ObjectCollection() { }

public ObjectCollection(Action<T> validator) => _validator = validator;

public int Count => _size;

public bool IsReadOnly => false;

public void Add(T item)
{
// Validate the item, either just by checking it for null, or using a custom validator,
// which should also check for null.
if (_validator is null)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
}
else
{
_validator.Invoke(item);
Debug.Assert(item != null);
}

if (_items is null)
{
// The collection is empty. Just store the new item directly.
_items = item;
_size = 1;
}
else if (_items is T existingItem)
{
// The collection has a single item stored directly. Upgrade to
// an array, and store both the existing and new items.
Debug.Assert(_size == 1);
T[] items = new T[DefaultSize];
items[0] = existingItem;
items[1] = item;
_items = items;
_size = 2;
}
else
{
T[] array = (T[])_items;
int size = _size;
if ((uint)size < (uint)array.Length)
{
// There's room in the existing array. Add the item.
array[size] = item;
}
else
{
// We need to grow the array. Do so, and store the new item.
Debug.Assert(_size > 0);
Debug.Assert(_size == array.Length);

var newItems = new T[array.Length * 2];
Array.Copy(array, newItems, size);
_items = newItems;
newItems[size] = item;
}
_size = size + 1;
}
}

public ObjectCollection(Action<T> validator)
: base(new List<T>())
public void Clear()
{
Debug.Assert(validator != null, $"{nameof(validator)} must not be null.");
_validator = validator;
_items = null;
_size = 0;
}

// This is only used internally to enumerate the collection
// without the enumerator allocation.
public new List<T>.Enumerator GetEnumerator()
public bool Contains(T item) =>
ReferenceEquals(item, _items) ||
(_size != 0 && _items is T[] items && Array.IndexOf(items, item, 0, _size) != -1);

public void CopyTo(T[] array, int arrayIndex)
{
return ((List<T>)Items).GetEnumerator();
if (_items is T[] items)
{
Array.Copy(items, 0, array, arrayIndex, _size);
}
else
{
Debug.Assert(_size == 0 || _size == 1);
if (array is null || _size > array.Length - arrayIndex)
{
// Use Array.CopyTo to throw the right exceptions.
new T[] { (T)_items! }.CopyTo(array!, arrayIndex);
}
else if (_size == 1)
{
array[arrayIndex] = (T)_items!;
}
}
}

protected override void InsertItem(int index, T item)
public bool Remove(T item)
{
_validator(item);
base.InsertItem(index, item);
if (ReferenceEquals(_items, item))
{
_items = null;
_size = 0;
return true;
}

if (_items is T[] items)
{
int index = Array.IndexOf(items, item, 0, _size);
if (index != -1)
{
_size--;
if (index < _size)
{
Array.Copy(items, index + 1, items, index, _size - index);
}
items[_size] = null!;

return true;
}
}

return false;
}

protected override void SetItem(int index, T item)
public Enumerator GetEnumerator() => new Enumerator(this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public struct Enumerator : IEnumerator<T>
{
_validator(item);
base.SetItem(index, item);
private readonly ObjectCollection<T> _list;
private int _index;
private T _current;

internal Enumerator(ObjectCollection<T> list)
{
_list = list;
_index = 0;
_current = default!;
}

public void Dispose() { }

public bool MoveNext()
{
ObjectCollection<T> list = _list;

if ((uint)_index < (uint)list._size)
{
_current = list._items is T[] items ? items[_index] : (T)list._items!;
_index++;
return true;
}

_index = _list._size + 1;
_current = default!;
return false;
}

public T Current => _current!;

object? IEnumerator.Current => _current;

void IEnumerator.Reset()
{
_index = 0;
_current = default!;
}
}

private static void CheckNotNull(T item)
internal sealed class DebugView
{
// Null values cannot be added to the collection.
if (item == null)
private readonly ObjectCollection<T> _collection;

public DebugView(ObjectCollection<T> collection) => _collection = collection ?? throw new ArgumentNullException(nameof(collection));

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
throw new ArgumentNullException(nameof(item));
get
{
T[] items = new T[_collection.Count];
_collection.CopyTo(items, 0);
return items;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,7 @@ public string Unit
}
}

public ICollection<RangeItemHeaderValue> Ranges
{
get
{
if (_ranges == null)
{
_ranges = new ObjectCollection<RangeItemHeaderValue>();
}
return _ranges;
}
}
public ICollection<RangeItemHeaderValue> Ranges => _ranges ??= new ObjectCollection<RangeItemHeaderValue>();

public RangeHeaderValue()
{
Expand Down
Loading