|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics.Hashing;
using System.Runtime.CompilerServices;
using System.Text;
namespace Microsoft.Extensions.Primitives
{
/// <summary>
/// Represents zero/null, one, or many strings in an efficient way.
/// </summary>
[DebuggerDisplay("{ToString()}")]
[DebuggerTypeProxy(typeof(StringValuesDebugView))]
public readonly struct StringValues : IList<string?>, IReadOnlyList<string?>, IEquatable<StringValues>, IEquatable<string?>, IEquatable<string?[]?>
{
/// <summary>
/// A readonly instance of the <see cref="StringValues"/> struct whose value is an empty string array.
/// </summary>
/// <remarks>
/// In application code, this field is most commonly used to safely represent a <see cref="StringValues"/> that has null string values.
/// </remarks>
public static readonly StringValues Empty = new StringValues(Array.Empty<string>());
private readonly object? _values;
/// <summary>
/// Initializes a new instance of the <see cref="StringValues"/> structure using the specified string.
/// </summary>
/// <param name="value">A string value or <see langword="null"/>.</param>
public StringValues(string? value)
{
_values = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="StringValues"/> structure using the specified array of strings.
/// </summary>
/// <param name="values">A string array.</param>
public StringValues(string?[]? values)
{
_values = values;
}
/// <summary>
/// Defines an implicit conversion of a given string to a <see cref="StringValues"/>.
/// </summary>
/// <param name="value">A string to implicitly convert.</param>
public static implicit operator StringValues(string? value)
{
return new StringValues(value);
}
/// <summary>
/// Defines an implicit conversion of a given string array to a <see cref="StringValues"/>.
/// </summary>
/// <param name="values">A string array to implicitly convert.</param>
public static implicit operator StringValues(string?[]? values)
{
return new StringValues(values);
}
/// <summary>
/// Defines an implicit conversion of a given <see cref="StringValues"/> to a string, with multiple values joined as a comma separated string.
/// </summary>
/// <remarks>
/// Returns <see langword="null"/> where <see cref="StringValues"/> has been initialized from an empty string array or is <see cref="StringValues.Empty"/>.
/// </remarks>
/// <param name="values">A <see cref="StringValues"/> to implicitly convert.</param>
public static implicit operator string?(StringValues values)
{
return values.GetStringValue();
}
/// <summary>
/// Defines an implicit conversion of a given <see cref="StringValues"/> to a string array.
/// </summary>
/// <param name="value">A <see cref="StringValues"/> to implicitly convert.</param>
public static implicit operator string?[]?(StringValues value)
{
return value.GetArrayValue();
}
/// <summary>
/// Gets the number of <see cref="string"/> elements contained in this <see cref="StringValues" />.
/// </summary>
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is null)
{
return 0;
}
if (value is string)
{
return 1;
}
else
{
// Not string, not null, can only be string[]
return Unsafe.As<string?[]>(value).Length;
}
}
}
bool ICollection<string?>.IsReadOnly => true;
/// <summary>
/// Gets the <see cref="string"/> at the specified index.
/// </summary>
/// <value>The string at the specified index.</value>
/// <param name="index">The zero-based index of the element to get.</param>
/// <exception cref="NotSupportedException">Set operations are not supported on readonly <see cref="StringValues"/>.</exception>
string? IList<string?>.this[int index]
{
get => this[index];
set => throw new NotSupportedException();
}
/// <summary>
/// Gets the <see cref="string"/> at the specified index.
/// </summary>
/// <value>The string at the specified index.</value>
/// <param name="index">The zero-based index of the element to get.</param>
public string? this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is string str)
{
if (index == 0)
{
return str;
}
}
else if (value != null)
{
// Not string, not null, can only be string[]
return Unsafe.As<string?[]>(value)[index]; // may throw
}
return OutOfBounds(); // throws
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static string OutOfBounds()
{
return Array.Empty<string>()[0]; // throws
}
/// <summary>
/// Converts the value of the current <see cref="StringValues"/> object to its equivalent string representation, with multiple values joined as a comma separated string.
/// </summary>
/// <returns>A string representation of the value of the current <see cref="StringValues"/> object.</returns>
public override string ToString()
{
return GetStringValue() ?? string.Empty;
}
private string? GetStringValue()
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is string s)
{
return s;
}
else
{
return GetStringValueFromArray(value);
}
static string? GetStringValueFromArray(object? value)
{
if (value is null)
{
return null;
}
Debug.Assert(value is string[]);
// value is not null or string, array, can only be string[]
string?[] values = Unsafe.As<string?[]>(value);
return values.Length switch
{
0 => null,
1 => values[0],
_ => GetJoinedStringValueFromArray(values),
};
}
static string GetJoinedStringValueFromArray(string?[] values)
{
// Calculate final length
int length = 0;
for (int i = 0; i < values.Length; i++)
{
string? value = values[i];
// Skip null and empty values
if (value != null && value.Length > 0)
{
if (length > 0)
{
// Add separator
length++;
}
length += value.Length;
}
}
#if NET
// Create the new string
return string.Create(length, values, (span, strings) => {
int offset = 0;
// Skip null and empty values
for (int i = 0; i < strings.Length; i++)
{
string? value = strings[i];
if (value != null && value.Length > 0)
{
if (offset > 0)
{
// Add separator
span[offset] = ',';
offset++;
}
value.AsSpan().CopyTo(span.Slice(offset));
offset += value.Length;
}
}
});
#else
var sb = new ValueStringBuilder(length);
bool hasAdded = false;
// Skip null and empty values
for (int i = 0; i < values.Length; i++)
{
string? value = values[i];
if (value != null && value.Length > 0)
{
if (hasAdded)
{
// Add separator
sb.Append(',');
}
sb.Append(value);
hasAdded = true;
}
}
return sb.ToString();
#endif
}
}
/// <summary>
/// Creates a string array from the current <see cref="StringValues"/> object.
/// </summary>
/// <returns>A string array represented by this instance.</returns>
/// <remarks>
/// <para>If the <see cref="StringValues"/> contains a single string internally, it is copied to a new array.</para>
/// <para>If the <see cref="StringValues"/> contains an array internally it returns that array instance.</para>
/// </remarks>
public string?[] ToArray()
{
return GetArrayValue() ?? Array.Empty<string>();
}
private string?[]? GetArrayValue()
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is string[] values)
{
return values;
}
else if (value != null)
{
// value not array, can only be string
return new[] { Unsafe.As<string>(value) };
}
else
{
return null;
}
}
/// <summary>
/// Returns the zero-based index of the first occurrence of an item in the <see cref="StringValues" />.
/// </summary>
/// <param name="item">The string to locate in the <see cref="StringValues"></see>.</param>
/// <returns>The zero-based index of the first occurrence of <paramref name="item" /> within the <see cref="StringValues"></see>, if found; otherwise, -1.</returns>
int IList<string?>.IndexOf(string? item)
{
return IndexOf(item);
}
private int IndexOf(string? item)
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is string[] values)
{
for (int i = 0; i < values.Length; i++)
{
if (string.Equals(values[i], item, StringComparison.Ordinal))
{
return i;
}
}
return -1;
}
if (value != null)
{
// value not array, can only be string
return string.Equals(Unsafe.As<string>(value), item, StringComparison.Ordinal) ? 0 : -1;
}
return -1;
}
/// <summary>Determines whether a string is in the <see cref="StringValues" />.</summary>
/// <param name="item">The <see cref="string"/> to locate in the <see cref="StringValues" />.</param>
/// <returns><see langword="true"/> if <paramref name="item" /> is found in the <see cref="StringValues" />; otherwise, <see langword="false"/>.</returns>
bool ICollection<string?>.Contains(string? item)
{
return IndexOf(item) >= 0;
}
/// <summary>
/// Copies the entire <see cref="StringValues" />to a string array, starting at the specified index of the target array.
/// </summary>
/// <param name="array">The one-dimensional <see cref="Array" /> that is the destination of the elements copied from. The <see cref="Array" /> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in the destination array at which copying begins.</param>
/// <exception cref="ArgumentNullException"><paramref name="array" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex" /> is less than 0.</exception>
/// <exception cref="ArgumentException">The number of elements in the source <see cref="StringValues" /> is greater than the available space from <paramref name="arrayIndex" /> to the end of the destination <paramref name="array" />.</exception>
void ICollection<string?>.CopyTo(string?[] array, int arrayIndex)
{
CopyTo(array, arrayIndex);
}
private void CopyTo(string?[] array, int arrayIndex)
{
// Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory
object? value = _values;
if (value is string[] values)
{
Array.Copy(values, 0, array, arrayIndex, values.Length);
return;
}
if (value != null)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (arrayIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (array.Length - arrayIndex < 1)
{
throw new ArgumentException(
$"'{nameof(array)}' is not long enough to copy all the items in the collection. Check '{nameof(arrayIndex)}' and '{nameof(array)}' length.");
}
// value not array, can only be string
array[arrayIndex] = Unsafe.As<string>(value);
}
}
void ICollection<string?>.Add(string? item) => throw new NotSupportedException();
void IList<string?>.Insert(int index, string? item) => throw new NotSupportedException();
bool ICollection<string?>.Remove(string? item) => throw new NotSupportedException();
void IList<string?>.RemoveAt(int index) => throw new NotSupportedException();
void ICollection<string?>.Clear() => throw new NotSupportedException();
/// <summary>Retrieves an object that can iterate through the individual strings in this <see cref="StringValues" />.</summary>
/// <returns>An enumerator that can be used to iterate through the <see cref="StringValues" />.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(_values);
}
/// <inheritdoc cref="GetEnumerator()" />
IEnumerator<string?> IEnumerable<string?>.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc cref="GetEnumerator()" />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Indicates whether the specified <see cref="StringValues"/> contains no string values.
/// </summary>
/// <param name="value">The <see cref="StringValues"/> to test.</param>
/// <returns><see langword="true"/> if <paramref name="value"/> contains a single <see langword="null"/> or empty string or an empty array; otherwise, <see langword="false"/>.</returns>
public static bool IsNullOrEmpty(StringValues value)
{
object? data = value._values;
if (data is null)
{
return true;
}
if (data is string[] values)
{
return values.Length switch
{
0 => true,
1 => string.IsNullOrEmpty(values[0]),
_ => false,
};
}
else
{
// Not array, can only be string
return string.IsNullOrEmpty(Unsafe.As<string>(data));
}
}
/// <summary>
/// Concatenates two specified instances of <see cref="StringValues"/>.
/// </summary>
/// <param name="values1">The first <see cref="StringValues"/> to concatenate.</param>
/// <param name="values2">The second <see cref="StringValues"/> to concatenate.</param>
/// <returns>The concatenation of <paramref name="values1"/> and <paramref name="values2"/>.</returns>
public static StringValues Concat(StringValues values1, StringValues values2)
{
int count1 = values1.Count;
int count2 = values2.Count;
if (count1 == 0)
{
return values2;
}
if (count2 == 0)
{
return values1;
}
var combined = new string[count1 + count2];
values1.CopyTo(combined, 0);
values2.CopyTo(combined, count1);
return new StringValues(combined);
}
/// <summary>
/// Concatenates specified instance of <see cref="StringValues"/> with specified <see cref="string"/>.
/// </summary>
/// <param name="values">The <see cref="StringValues"/> to concatenate.</param>
/// <param name="value">The <see cref="string" /> to concatenate.</param>
/// <returns>The concatenation of <paramref name="values"/> and <paramref name="value"/>.</returns>
public static StringValues Concat(in StringValues values, string? value)
{
if (value == null)
{
return values;
}
int count = values.Count;
if (count == 0)
{
return new StringValues(value);
}
var combined = new string[count + 1];
values.CopyTo(combined, 0);
combined[count] = value;
return new StringValues(combined);
}
/// <summary>
/// Concatenates specified instance of <see cref="string"/> with specified <see cref="StringValues"/>.
/// </summary>
/// <param name="value">The <see cref="string" /> to concatenate.</param>
/// <param name="values">The <see cref="StringValues"/> to concatenate.</param>
/// <returns>The concatenation of <paramref name="values"/> and <paramref name="values"/>.</returns>
public static StringValues Concat(string? value, in StringValues values)
{
if (value == null)
{
return values;
}
int count = values.Count;
if (count == 0)
{
return new StringValues(value);
}
var combined = new string[count + 1];
combined[0] = value;
values.CopyTo(combined, 1);
return new StringValues(combined);
}
/// <summary>
/// Determines whether two specified <see cref="StringValues"/> objects have the same values in the same order.
/// </summary>
/// <param name="left">The first <see cref="StringValues"/> to compare.</param>
/// <param name="right">The second <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool Equals(StringValues left, StringValues right)
{
int count = left.Count;
if (count != right.Count)
{
return false;
}
for (int i = 0; i < count; i++)
{
if (left[i] != right[i])
{
return false;
}
}
return true;
}
/// <summary>
/// Determines whether two specified <see cref="StringValues"/> have the same values.
/// </summary>
/// <param name="left">The first <see cref="StringValues"/> to compare.</param>
/// <param name="right">The second <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator ==(StringValues left, StringValues right)
{
return Equals(left, right);
}
/// <summary>
/// Determines whether two specified <see cref="StringValues"/> have different values.
/// </summary>
/// <param name="left">The first <see cref="StringValues"/> to compare.</param>
/// <param name="right">The second <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different to the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(StringValues left, StringValues right)
{
return !Equals(left, right);
}
/// <summary>
/// Determines whether this instance and another specified <see cref="StringValues"/> object have the same values.
/// </summary>
/// <param name="other">The string to compare to this instance.</param>
/// <returns><see langword="true" /> if the value of <paramref name="other" /> is the same as the value of this instance; otherwise, <see langword="false" />.</returns>
public bool Equals(StringValues other) => Equals(this, other);
/// <summary>
/// Determines whether the specified <see cref="string"/> and <see cref="StringValues"/> objects have the same values.
/// </summary>
/// <param name="left">The <see cref="string"/> to compare.</param>
/// <param name="right">The <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>. If <paramref name="left"/> is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
public static bool Equals(string? left, StringValues right) => Equals(new StringValues(left), right);
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and <see cref="string"/> objects have the same values.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The <see cref="string"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>. If <paramref name="right"/> is <see langword="null"/>, the method returns <see langword="false"/>.</returns>
public static bool Equals(StringValues left, string? right) => Equals(left, new StringValues(right));
/// <summary>
/// Determines whether this instance and a specified <see cref="string"/>, have the same value.
/// </summary>
/// <param name="other">The <see cref="string"/> to compare to this instance.</param>
/// <returns><see langword="true"/> if the value of <paramref name="other"/> is the same as this instance; otherwise, <see langword="false"/>. If <paramref name="other"/> is <see langword="null"/>, returns <see langword="false"/>.</returns>
public bool Equals(string? other) => Equals(this, new StringValues(other));
/// <summary>
/// Determines whether the specified string array and <see cref="StringValues"/> objects have the same values.
/// </summary>
/// <param name="left">The string array to compare.</param>
/// <param name="right">The <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool Equals(string?[]? left, StringValues right) => Equals(new StringValues(left), right);
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and string array objects have the same values.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The string array to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool Equals(StringValues left, string?[]? right) => Equals(left, new StringValues(right));
/// <summary>
/// Determines whether this instance and a specified string array have the same values.
/// </summary>
/// <param name="other">The string array to compare to this instance.</param>
/// <returns><see langword="true"/> if the value of <paramref name="other"/> is the same as this instance; otherwise, <see langword="false"/>.</returns>
public bool Equals(string?[]? other) => Equals(this, new StringValues(other));
/// <inheritdoc cref="Equals(StringValues, string)" />
public static bool operator ==(StringValues left, string? right) => Equals(left, new StringValues(right));
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and <see cref="string"/> objects have different values.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The <see cref="string"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different to the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(StringValues left, string? right) => !Equals(left, new StringValues(right));
/// <inheritdoc cref="Equals(string, StringValues)" />
public static bool operator ==(string? left, StringValues right) => Equals(new StringValues(left), right);
/// <summary>
/// Determines whether the specified <see cref="string"/> and <see cref="StringValues"/> objects have different values.
/// </summary>
/// <param name="left">The <see cref="string"/> to compare.</param>
/// <param name="right">The <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different to the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(string? left, StringValues right) => !Equals(new StringValues(left), right);
/// <inheritdoc cref="Equals(StringValues, string[])" />
public static bool operator ==(StringValues left, string?[]? right) => Equals(left, new StringValues(right));
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and string array have different values.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The string array to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different to the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(StringValues left, string?[]? right) => !Equals(left, new StringValues(right));
/// <inheritdoc cref="Equals(string[], StringValues)" />
public static bool operator ==(string?[]? left, StringValues right) => Equals(new StringValues(left), right);
/// <summary>
/// Determines whether the specified string array and <see cref="StringValues"/> have different values.
/// </summary>
/// <param name="left">The string array to compare.</param>
/// <param name="right">The <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different to the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(string?[]? left, StringValues right) => !Equals(new StringValues(left), right);
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and <see cref="object"/>, which must be a
/// <see cref="StringValues"/>, <see cref="string"/>, or array of <see cref="string"/>, have the same value.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The <see cref="object"/> to compare.</param>
/// <returns><see langword="true"/> if the <paramref name="left"/> object is equal to the <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator ==(StringValues left, object? right) => left.Equals(right);
/// <summary>
/// Determines whether the specified <see cref="StringValues"/> and <see cref="object"/>, which must be a
/// <see cref="StringValues"/>, <see cref="string"/>, or array of <see cref="string"/>, have different values.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The <see cref="object"/> to compare.</param>
/// <returns><see langword="true"/> if the <paramref name="left"/> object is equal to the <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(StringValues left, object? right) => !left.Equals(right);
/// <summary>
/// Determines whether the specified <see cref="object"/>, which must be a
/// <see cref="StringValues"/>, <see cref="string"/>, or array of <see cref="string"/>, and specified <see cref="StringValues"/>, have the same value.
/// </summary>
/// <param name="left">The <see cref="StringValues"/> to compare.</param>
/// <param name="right">The <see cref="object"/> to compare.</param>
/// <returns><see langword="true"/> if the <paramref name="left"/> object is equal to the <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator ==(object? left, StringValues right) => right.Equals(left);
/// <summary>
/// Determines whether the specified <see cref="object"/> and <see cref="StringValues"/> object have the same values.
/// </summary>
/// <param name="left">The <see cref="object"/> to compare.</param>
/// <param name="right">The <see cref="StringValues"/> to compare.</param>
/// <returns><see langword="true"/> if the <paramref name="left"/> object is equal to the <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(object? left, StringValues right) => !right.Equals(left);
/// <summary>
/// Determines whether this instance and a specified object have the same value.
/// </summary>
/// <param name="obj">An object to compare with this object.</param>
/// <returns><see langword="true"/> if the current object is equal to <paramref name="obj"/>; otherwise, <see langword="false"/>.</returns>
public override bool Equals(object? obj)
{
if (obj == null)
{
return Equals(this, StringValues.Empty);
}
if (obj is string str)
{
return Equals(this, str);
}
if (obj is string[] array)
{
return Equals(this, array);
}
if (obj is StringValues stringValues)
{
return Equals(this, stringValues);
}
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode()
{
object? value = _values;
if (value is string[] values)
{
if (Count == 1)
{
return Unsafe.As<string>(this[0])?.GetHashCode() ?? Count.GetHashCode();
}
int hashCode = 0;
for (int i = 0; i < values.Length; i++)
{
hashCode = HashHelpers.Combine(hashCode, values[i]?.GetHashCode() ?? 0);
}
return hashCode;
}
else
{
return Unsafe.As<string>(value)?.GetHashCode() ?? Count.GetHashCode();
}
}
/// <summary>
/// Enumerates the string values of a <see cref="StringValues" />.
/// </summary>
public struct Enumerator : IEnumerator<string?>
{
private readonly string?[]? _values;
private int _index;
private string? _current;
internal Enumerator(object? value)
{
if (value is string str)
{
_values = null;
_current = str;
}
else
{
_current = null;
_values = Unsafe.As<string?[]>(value);
}
_index = 0;
}
/// <summary>
/// Instantiates an <see cref="Enumerator"/> using a <see cref="StringValues"/>.
/// </summary>
/// <param name="values">The <see cref="StringValues"/> to enumerate.</param>
public Enumerator(ref StringValues values) : this(values._values)
{ }
/// <summary>
/// Advances the enumerator to the next element of the <see cref="StringValues"/>.
/// </summary>
/// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the <see cref="StringValues"/>.</returns>
public bool MoveNext()
{
int index = _index;
if (index < 0)
{
return false;
}
string?[]? values = _values;
if (values != null)
{
if ((uint)index < (uint)values.Length)
{
_index = index + 1;
_current = values[index];
return true;
}
_index = -1;
return false;
}
_index = -1; // sentinel value
return _current != null;
}
/// <summary>
/// Gets the element at the current position of the enumerator.
/// </summary>
public string? Current => _current;
object? IEnumerator.Current => _current;
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
/// <summary>
/// Releases all resources used by the <see cref="Enumerator" />.
/// </summary>
public void Dispose()
{
}
}
private sealed class StringValuesDebugView(StringValues values)
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public string?[] Items => values.ToArray();
}
}
}
|