ArrayBackedPropertyBag<TKey, TValue>
A property bag which is optimized for storage of a small number of items.
If the item count is less than 2, there are no allocations. Any additional items are stored in an array which will grow as needed.
MUST be passed by ref only.
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace System.ClientModel.Internal
{
internal struct ArrayBackedPropertyBag<TKey, [System.Runtime.CompilerServices.Nullable(2)] TValue> where TKey : struct, IEquatable<TKey>
{
[System.Runtime.CompilerServices.Nullable(new byte[] {
0,
0,
2
})]
private (TKey Key, TValue Value) _first;
[System.Runtime.CompilerServices.Nullable(new byte[] {
0,
0,
2
})]
private (TKey Key, TValue Value) _second;
[System.Runtime.CompilerServices.Nullable(new byte[] {
2,
0,
0,
2
})]
private (TKey Key, TValue Value)[] _rest;
private int _count;
[System.Runtime.CompilerServices.Nullable(1)]
private readonly object _lock;
public int Count => _count;
public bool IsEmpty => _count == 0;
public ArrayBackedPropertyBag()
{
_first = default((TKey, TValue));
_second = default((TKey, TValue));
_rest = null;
_count = 0;
_lock = new object();
}
public void GetAt(int index, out TKey key, [System.Runtime.CompilerServices.Nullable(2)] out TValue value)
{
(TKey, TValue) valueTuple;
switch (index) {
case 0:
valueTuple = _first;
break;
case 1:
valueTuple = _second;
break;
default:
valueTuple = GetRest()[index - 2];
break;
}
(TKey, TValue) valueTuple2 = valueTuple;
key = valueTuple2.Item1;
value = valueTuple2.Item2;
}
public bool TryGetValue(TKey key, [System.Runtime.CompilerServices.Nullable(2)] [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TValue value)
{
int index = GetIndex(key);
if (index < 0) {
value = default(TValue);
return false;
}
value = GetAt(index);
return true;
}
public bool TryAdd(TKey key, [System.Runtime.CompilerServices.Nullable(1)] TValue value, [System.Runtime.CompilerServices.Nullable(2)] out TValue existingValue)
{
int index = GetIndex(key);
if (index >= 0) {
existingValue = GetAt(index);
return false;
}
AddInternal(key, value);
existingValue = default(TValue);
return true;
}
public void Set(TKey key, [System.Runtime.CompilerServices.Nullable(2)] TValue value)
{
int index = GetIndex(key);
if (index < 0)
AddInternal(key, value);
else
SetAt(index, (key, value));
}
public bool TryRemove(TKey key)
{
switch (_count) {
case 0:
return false;
case 1:
if (IsFirst(key)) {
_first = default((TKey, TValue));
_count--;
return true;
}
return false;
case 2:
if (IsFirst(key)) {
_first = _second;
_second = default((TKey, TValue));
_count--;
return true;
}
if (IsSecond(key)) {
_second = default((TKey, TValue));
_count--;
return true;
}
return false;
default: {
(TKey, TValue)[] rest = GetRest();
if (IsFirst(key)) {
_first = _second;
_second = rest[0];
_count--;
Array.Copy(rest, 1, rest, 0, _count - 2);
rest[_count - 2] = default((TKey, TValue));
return true;
}
if (IsSecond(key)) {
_second = rest[0];
_count--;
Array.Copy(rest, 1, rest, 0, _count - 2);
rest[_count - 2] = default((TKey, TValue));
return true;
}
for (int i = 0; i < _count - 2; i++) {
if (rest[i].Item1.Equals(key)) {
_count--;
Array.Copy(rest, i + 1, rest, i, _count - 2 - i);
rest[_count - 2] = default((TKey, TValue));
return true;
}
}
return false;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsFirst(TKey key)
{
return _first.Key.Equals(key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsSecond(TKey key)
{
return _second.Key.Equals(key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddInternal(TKey key, [System.Runtime.CompilerServices.Nullable(2)] TValue value)
{
switch (_count) {
case 0:
_first = (key, value);
_count = 1;
break;
case 1:
if (IsFirst(key))
_first = (_first.Key, value);
else {
_second = (key, value);
_count = 2;
}
break;
default:
if (_rest == null) {
_rest = ArrayPool<(TKey, TValue)>.Shared.Rent(8);
_rest[_count++ - 2] = (key, value);
} else {
if (_rest.Length <= _count) {
(TKey, TValue)[] array = ArrayPool<(TKey, TValue)>.Shared.Rent(_rest.Length << 1);
_rest.CopyTo(array, 0);
(TKey, TValue)[] rest = _rest;
_rest = array;
ArrayPool<(TKey, TValue)>.Shared.Return(rest, true);
}
_rest[_count++ - 2] = (key, value);
}
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetAt(int index, [System.Runtime.CompilerServices.Nullable(new byte[] {
0,
0,
2
})] (TKey Key, TValue Value) value)
{
switch (index) {
case 0:
_first = value;
break;
case 1:
_second = value;
break;
default:
GetRest()[index - 2] = value;
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Runtime.CompilerServices.NullableContext(2)]
private TValue GetAt(int index)
{
switch (index) {
case 0:
return _first.Value;
case 1:
return _second.Value;
default:
return GetRest()[index - 2].Value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetIndex(TKey key)
{
if (_count == 0)
return -1;
if (_count > 0 && _first.Key.Equals(key))
return 0;
if (_count > 1 && _second.Key.Equals(key))
return 1;
if (_count <= 2)
return -1;
(TKey, TValue)[] rest = GetRest();
int num = _count - 2;
for (int i = 0; i < num; i++) {
if (rest[i].Item1.Equals(key))
return i + 2;
}
return -1;
}
public void Dispose()
{
_count = 0;
_first = default((TKey, TValue));
_second = default((TKey, TValue));
lock (_lock) {
if (_rest != null) {
(TKey, TValue)[] rest = _rest;
_rest = null;
ArrayPool<(TKey, TValue)>.Shared.Return(rest, true);
}
}
}
[return: System.Runtime.CompilerServices.Nullable(new byte[] {
1,
0,
0,
2
})]
private (TKey Key, TValue Value)[] GetRest()
{
(TKey Key, TValue Value)[] rest = _rest;
if (rest == null)
throw new InvalidOperationException(string.Format("{0} field is null while {1} == {2}", "_rest", "_count", _count));
return rest;
}
[Conditional("DEBUG")]
private void CheckDisposed()
{
}
}
}