Added FisheryLib.Collections.FishTable and Mono.Cecil extension methods
A bit of refactoring has been done too
This commit is contained in:
@@ -7,15 +7,20 @@ using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using FisheryLib.Collections;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FisheryLib;
|
||||
|
||||
[PublicAPI]
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
public static bool TryGetItem<T>(this List<T> list, int index, [NotNullWhen(true)] out T? item)
|
||||
{
|
||||
if (list.Count > index)
|
||||
if (list._size > index)
|
||||
{
|
||||
item = list[index]!;
|
||||
item = list._items[index]!;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,19 +29,23 @@ public static class CollectionExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Taken from Span<>.this[] and slower than List<>.this[] due to the ArrayAdjustment
|
||||
/// Taken from Span.this[] and slower than List.this[] due to the ArrayAdjustment
|
||||
/// </summary>
|
||||
internal static T GetItemUnchecked<T>(this List<T> list, int index)
|
||||
=> Unsafe.Add(ref Unsafe.AddByteOffset(ref Unsafe.As<Pinnable<T>>(list._items).Data, PerTypeValues<T>.ArrayAdjustment), index)!;
|
||||
=> Unsafe.Add(
|
||||
ref Unsafe.AddByteOffset(ref Unsafe.As<Pinnable<T>>(list._items).Data, PerTypeValues<T>.ArrayAdjustment),
|
||||
index)!;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private sealed class Pinnable<T>
|
||||
{
|
||||
public T? Data;
|
||||
}
|
||||
|
||||
private static class PerTypeValues<T>
|
||||
{
|
||||
public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment();
|
||||
|
||||
private static IntPtr MeasureArrayAdjustment()
|
||||
{
|
||||
var array = new T[1];
|
||||
@@ -60,8 +69,8 @@ public static class CollectionExtensions
|
||||
{
|
||||
var array = new T[length];
|
||||
|
||||
if (enumerable is IList<T> ilist)
|
||||
array.Fill(ilist);
|
||||
if (enumerable is IList<T> iList)
|
||||
array.Fill(iList);
|
||||
else
|
||||
array.FillUsingEnumerable(enumerable);
|
||||
|
||||
@@ -81,15 +90,16 @@ public static class CollectionExtensions
|
||||
{
|
||||
Guard.IsNotNull(destination);
|
||||
|
||||
if (enumerable is IList<T> ilist)
|
||||
destination.Fill(ilist);
|
||||
if (enumerable is IList<T> iList)
|
||||
destination.Fill(iList);
|
||||
else
|
||||
destination.FillUsingEnumerable(enumerable);
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static void FillUsingEnumerable<T>(this T[] array, IEnumerable<T> enumerable, int startIndex = 0, int count = -1)
|
||||
private static void FillUsingEnumerable<T>(this T[] array, IEnumerable<T> enumerable, int startIndex = 0,
|
||||
int count = -1)
|
||||
{
|
||||
var i = 0u;
|
||||
foreach (var item in enumerable)
|
||||
@@ -102,7 +112,8 @@ public static class CollectionExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fill<Telement, Tcollection>(this Telement[] array, Tcollection collection, int startIndex = 0, int count = -1)
|
||||
public static void Fill<Telement, Tcollection>(this Telement[] array, Tcollection collection, int startIndex = 0,
|
||||
int count = -1)
|
||||
where Tcollection : IList<Telement>
|
||||
{
|
||||
Guard.IsNotNull(collection);
|
||||
@@ -124,8 +135,8 @@ public static class CollectionExtensions
|
||||
{
|
||||
Guard.IsNotNull(enumerable);
|
||||
|
||||
if (enumerable is IList<T> ilist)
|
||||
array.Fill(ilist, startIndex, count);
|
||||
if (enumerable is IList<T> iList)
|
||||
array.Fill(iList, startIndex, count);
|
||||
else
|
||||
array.FillUsingEnumerable(enumerable, startIndex, count);
|
||||
}
|
||||
@@ -146,6 +157,27 @@ public static class CollectionExtensions
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ref ThrowKeyNotFoundException<TKey, TValue>(key);
|
||||
}
|
||||
|
||||
public static ref TValue GetReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
|
||||
{
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
|
||||
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
|
||||
return ref entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ref ThrowKeyNotFoundException<TKey, TValue>(key);
|
||||
}
|
||||
|
||||
@@ -153,6 +185,7 @@ public static class CollectionExtensions
|
||||
/// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not.
|
||||
/// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The instance</param>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>ref TValue or Unsafe.NullRef{TValue}</returns>
|
||||
public static ref TValue TryGetReferenceUnsafe<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
|
||||
@@ -171,11 +204,11 @@ public static class CollectionExtensions
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ref Unsafe.NullRef<TValue>();
|
||||
}
|
||||
|
||||
public static ref TValue TryGetReferenceUnsafe<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
|
||||
where TKey : struct
|
||||
{
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
@@ -185,12 +218,13 @@ public static class CollectionExtensions
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && entry.key.Equals(ref key))
|
||||
if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
|
||||
return ref entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ref Unsafe.NullRef<TValue>();
|
||||
}
|
||||
|
||||
@@ -210,6 +244,7 @@ public static class CollectionExtensions
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -230,8 +265,161 @@ public static class CollectionExtensions
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TValue GetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
|
||||
where TValue : new()
|
||||
{
|
||||
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
|
||||
|
||||
StartOfLookup:
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && entry.key.Equals<TKey>(key))
|
||||
return ref entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.Add(key, Reflection.New<TValue>());
|
||||
goto StartOfLookup;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TValue GetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
|
||||
where TValue : new()
|
||||
{
|
||||
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
|
||||
|
||||
StartOfLookup:
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
|
||||
return ref entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.Add(key, Reflection.New<TValue>());
|
||||
goto StartOfLookup;
|
||||
}
|
||||
|
||||
// public static bool TryGetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key,
|
||||
// ref TValue reference)
|
||||
// {
|
||||
// var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
|
||||
// var result = true;
|
||||
//
|
||||
// StartOfLookup:
|
||||
// if (dictionary.buckets != null)
|
||||
// {
|
||||
// var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
// while (bucket >= 0)
|
||||
// {
|
||||
// ref var entry = ref dictionary.entries[bucket];
|
||||
// if (entry.hashCode == keyCode && entry.key.Equals<TKey>(key))
|
||||
// {
|
||||
// reference.Value = ref entry.value;
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// bucket = entry.next;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result = false;
|
||||
// dictionary.Add(key, Reflection.New<TValue>());
|
||||
// goto StartOfLookup;
|
||||
// }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
|
||||
where TValue : new()
|
||||
{
|
||||
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
|
||||
|
||||
StartOfLookup:
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && entry.key.Equals<TKey>(key))
|
||||
return entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.Add(key, Reflection.New<TValue>());
|
||||
goto StartOfLookup;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
|
||||
where TValue : new()
|
||||
{
|
||||
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
|
||||
|
||||
StartOfLookup:
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
|
||||
return entry.value;
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
dictionary.Add(key, Reflection.New<TValue>());
|
||||
goto StartOfLookup;
|
||||
}
|
||||
|
||||
public static bool TryGetOrAddValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key,
|
||||
out TValue value)
|
||||
{
|
||||
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
|
||||
var result = true;
|
||||
|
||||
StartOfLookup:
|
||||
if (dictionary.buckets != null)
|
||||
{
|
||||
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
|
||||
while (bucket >= 0)
|
||||
{
|
||||
ref var entry = ref dictionary.entries[bucket];
|
||||
if (entry.hashCode == keyCode && entry.key.Equals<TKey>(key))
|
||||
{
|
||||
value = entry.value;
|
||||
return result;
|
||||
}
|
||||
|
||||
bucket = entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
result = false;
|
||||
dictionary.Add(key, Reflection.New<TValue>());
|
||||
goto StartOfLookup;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static ref V ThrowKeyNotFoundException<T, V>(T key)
|
||||
@@ -243,42 +431,106 @@ public static class CollectionExtensions
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowKeyNotFoundException(object key)
|
||||
=> throw new KeyNotFoundException(
|
||||
string.Format("The given key '{0}' was not present in the dictionary.", key));
|
||||
=> throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref int Version<T>(this List<T> list) => ref list._version;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AddRangeFast<T>(this List<T> list, List<T> range)
|
||||
{
|
||||
if (range._size < 1)
|
||||
return;
|
||||
|
||||
list.EnsureCapacity(list._size + range._size);
|
||||
|
||||
range.UnsafeCopyTo(list, list._size);
|
||||
|
||||
list._size += range._size;
|
||||
list._version++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnsafeCopyTo<T>(this List<T> source, List<T> destination, int destinationStartIndex)
|
||||
=> UnsafeBlockCopy(ref source._items[0], ref destination._items[destinationStartIndex], source._size);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnsafeCopyTo<T>(this T[] source, T[] destination, int destinationStartIndex)
|
||||
=> UnsafeBlockCopy(ref source[0], ref destination[destinationStartIndex], source.Length);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void UnsafeBlockCopy<T>(ref T source, ref T destination, int count)
|
||||
=> Unsafe.CopyBlock(ref Unsafe.As<T, byte>(ref destination),
|
||||
ref Unsafe.As<T, byte>(ref source),
|
||||
(uint)((nint)sizeof(T) * count));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
|
||||
/// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are
|
||||
/// modified.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] ItemsUnchecked<T>(this List<T> list) => list._items;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
|
||||
/// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every
|
||||
/// time items are modified.
|
||||
/// </summary>
|
||||
public static Span<T> AsSpanUnchecked<T>(this List<T> list) => new(list._items, 0, list.Count);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpanUnchecked<T>(this List<T> list) => new(list._items, 0, list._size);
|
||||
|
||||
public static ReadOnlySpan<T> AsReadOnlySpan<T>(this List<T> list) => new(list._items, 0, list.Count);
|
||||
|
||||
public static void ReplaceContentsWith<T, V>(this List<T> list, V collection) where V : ICollection<T>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan<T> AsReadOnlySpan<T>(this List<T> list) => new(list._items, 0, list._size);
|
||||
|
||||
public static void ReplaceContentsWith<T>(this List<T> list, [AllowNull] List<T> collection)
|
||||
{
|
||||
if (ReferenceEquals(list, collection))
|
||||
return;
|
||||
|
||||
if (collection is null
|
||||
|| (collection.Count is var collectionCount
|
||||
&& collectionCount == 0))
|
||||
|| (collection._size is var collectionCount
|
||||
&& collectionCount == 0))
|
||||
{
|
||||
list.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectionCount <= list.Count)
|
||||
if (collectionCount <= list._size)
|
||||
{
|
||||
if (collectionCount < list.Count)
|
||||
list.RemoveRange(collectionCount, list.Count - collectionCount);
|
||||
if (collectionCount < list._size)
|
||||
list.RemoveRange(collectionCount, list._size - collectionCount);
|
||||
|
||||
collection.UnsafeCopyTo(list, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
list._items = new T[collectionCount];
|
||||
collection.UnsafeCopyTo(list, 0);
|
||||
list._size = collectionCount;
|
||||
}
|
||||
|
||||
list._version++;
|
||||
}
|
||||
|
||||
public static void ReplaceContentsWith<T, V>(this List<T> list, [AllowNull] V collection) where V : ICollection<T>
|
||||
{
|
||||
if (typeof(V) == typeof(List<T>))
|
||||
list.ReplaceContentsWith(Unsafe.As<List<T>>(collection));
|
||||
|
||||
if (ReferenceEquals(list, collection))
|
||||
return;
|
||||
|
||||
if (collection is null
|
||||
|| (collection.Count is var collectionCount
|
||||
&& collectionCount == 0))
|
||||
{
|
||||
list.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectionCount <= list._size)
|
||||
{
|
||||
if (collectionCount < list._size)
|
||||
list.RemoveRange(collectionCount, list._size - collectionCount);
|
||||
collection.CopyTo(list._items, 0);
|
||||
}
|
||||
else
|
||||
@@ -287,18 +539,176 @@ public static class CollectionExtensions
|
||||
collection.CopyTo(list._items, 0);
|
||||
list._size = collectionCount;
|
||||
}
|
||||
|
||||
list._version++;
|
||||
}
|
||||
|
||||
public static FishTable<TKey, TSource> ToFishTable<TSource, TKey>(this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector)
|
||||
=> source.ToFishTable(keySelector, static item => item);
|
||||
|
||||
public static FishTable<TKey, TValue> ToFishTable<TSource, TKey, TValue>(this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector, Func<TSource, TValue> valueSelector)
|
||||
{
|
||||
Guard.IsNotNull(source);
|
||||
Guard.IsNotNull(keySelector);
|
||||
Guard.IsNotNull(valueSelector);
|
||||
|
||||
var capacity = 0;
|
||||
if (source is ICollection iCollection)
|
||||
capacity = (int)(iCollection.Count * 1.5f);
|
||||
|
||||
var fishTable = new FishTable<TKey, TValue>(capacity);
|
||||
fishTable.AddRange(source, keySelector, valueSelector);
|
||||
return fishTable;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Clear<T>(this T[] array) => Array.Clear(array, 0, array.Length);
|
||||
|
||||
// public static List<T> Copy<T>(this List<T> source)
|
||||
// {
|
||||
// Guard.IsNotNull(source);
|
||||
//
|
||||
// var destination = new List<T>(source._size);
|
||||
//
|
||||
// if (source._size == 0)
|
||||
// return destination;
|
||||
//
|
||||
// // Unsafe.CopyBlock(ref Unsafe.As<T, byte>(ref destination._items[0]),
|
||||
// // ref Unsafe.As<T, byte>(ref source._items[0]), (uint)sizeof(T) * (uint)source._size);
|
||||
//
|
||||
// FisheryLib.CollectionExtensions.UnsafeBlockCopy(ref source._items[0], // does a type check that causes issues
|
||||
// ref destination._items[0], source._size); // when called from transpiled methods
|
||||
//
|
||||
// destination._size = source._size;
|
||||
//
|
||||
// return destination;
|
||||
// }
|
||||
|
||||
public static unsafe List<T> Copy<T>(this List<T> source)
|
||||
{
|
||||
Guard.IsNotNull(source);
|
||||
|
||||
var destination = new List<T>(source._size);
|
||||
|
||||
if (source._size == 0)
|
||||
return destination;
|
||||
|
||||
fixed (void* destinationPointer = &destination._items[0], sourcePointer = &source._items[0])
|
||||
Unsafe.CopyBlock(destinationPointer, sourcePointer, (uint)(source._size * sizeof(T)));
|
||||
|
||||
destination._size = source._size;
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AddRangeFast<T>(this List<T> list, T[] range)
|
||||
{
|
||||
Guard.IsNotNull(list);
|
||||
Guard.IsNotNull(range);
|
||||
|
||||
if (range.Length < 1)
|
||||
return;
|
||||
|
||||
list.EnsureCapacity(list._size + range.Length);
|
||||
range.UnsafeCopyTo(list._items, list._size);
|
||||
|
||||
list._size += range.Length;
|
||||
list._version++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add(this List<char> list, string value)
|
||||
{
|
||||
Guard.IsNotNull(list);
|
||||
Guard.IsNotNull(value);
|
||||
|
||||
if (value.Length < 1)
|
||||
return;
|
||||
|
||||
list.EnsureCapacity(list._size + value.Length);
|
||||
UnsafeBlockCopy(ref value.m_firstChar,
|
||||
ref list._items[list._size], value.Length);
|
||||
|
||||
list._size += value.Length;
|
||||
list._version++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static StringBuilder Append(this StringBuilder builder, List<char> value)
|
||||
=> builder.Append(value._items, 0, value._size);
|
||||
|
||||
public static ref T GetReferenceUnverifiable<T>(this List<T> list, int index) => ref list._items[index];
|
||||
|
||||
public static IList TryNew(IEnumerable enumerable, Type type) => GetListExtensionMethods(type).New(enumerable);
|
||||
|
||||
public static void TryAddRange<T>(this IList list, T collection, Type type) where T : ICollection => GetListExtensionMethods(type).AddRange(list, collection);
|
||||
public static void TryAddRange<T>(this IList list, T collection, Type type) where T : ICollection
|
||||
=> GetListExtensionMethods(type).AddRange(list, collection);
|
||||
|
||||
public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list);
|
||||
|
||||
public static unsafe uint GetSizeEstimate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
|
||||
=> dictionary.GetType().ComputeManagedObjectSizeEstimate()
|
||||
+ ((uint)sizeof(Dictionary<TKey, TValue>.Entry) * (uint)(dictionary.entries?.Length ?? 0))
|
||||
+ (sizeof(int) * (uint)(dictionary.buckets?.Length ?? 0));
|
||||
|
||||
internal static unsafe uint ComputeManagedObjectSizeEstimate(this Type type)
|
||||
{
|
||||
var objectHeader = (uint)sizeof(void*) * 2u;
|
||||
ref var cachedSize = ref CachedManagedObjectSizes.GetOrAddReference(type.TypeHandle.Value);
|
||||
|
||||
if (cachedSize.Size == uint.MaxValue)
|
||||
UpdateCachedManagedObjectSize(type, ref cachedSize);
|
||||
|
||||
return objectHeader + cachedSize.Size;
|
||||
}
|
||||
|
||||
private static void UpdateCachedManagedObjectSize(Type type, ref ManagedObjectSize cachedSize)
|
||||
{
|
||||
cachedSize.Size = 0;
|
||||
var fields = type.GetFields(BindingFlags.Instance
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic);
|
||||
|
||||
for (var i = fields.Length; i-- > 0;)
|
||||
{
|
||||
var fieldType = fields[i].FieldType;
|
||||
if (!typeof(ValueType).IsAssignableFrom(fieldType) || fieldType == typeof(ValueType))
|
||||
// IsValueType and IsSubclassOf somehow cause StackOverflows here
|
||||
cachedSize.Size += (uint)IntPtr.Size;
|
||||
else if (fieldType.IsPrimitive)
|
||||
cachedSize.Size += (uint)Marshal.SizeOf(fieldType);
|
||||
else if (fieldType != type)
|
||||
cachedSize.Size += fieldType.ComputeManagedObjectSizeEstimate();
|
||||
else
|
||||
cachedSize.Size += (uint)IntPtr.Size; // fuck it
|
||||
}
|
||||
|
||||
var remainder = cachedSize.Size % (uint)IntPtr.Size;
|
||||
if (remainder != 0u)
|
||||
cachedSize.Size += (uint)IntPtr.Size - remainder;
|
||||
}
|
||||
|
||||
private static FishTable<IntPtr, ManagedObjectSize> CachedManagedObjectSizes => _cachedManagedObjectSizes ??= new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static FishTable<IntPtr, ManagedObjectSize>? _cachedManagedObjectSizes;
|
||||
|
||||
private struct ManagedObjectSize
|
||||
{
|
||||
public uint Size = uint.MaxValue;
|
||||
|
||||
[UsedImplicitly]
|
||||
public ManagedObjectSize()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static GenericBase GetListExtensionMethods(Type type)
|
||||
=> ListExtensionCache.TryGetValue(type)
|
||||
?? CreateListExtensionMethods(type);
|
||||
?? CreateListExtensionMethods(type);
|
||||
|
||||
private static GenericBase CreateListExtensionMethods(Type type)
|
||||
{
|
||||
@@ -322,7 +732,8 @@ public static class CollectionExtensions
|
||||
public class Generic<T> : GenericBase
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void AddRange(IList list, ICollection collection) => ((List<T>)list).AddRange((IEnumerable<T>)collection);
|
||||
public override void AddRange(IList list, ICollection collection)
|
||||
=> ((List<T>)list).AddRange((IEnumerable<T>)collection);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override Array ToArray(IList list) => ((List<T>)list).ToArray();
|
||||
|
||||
Reference in New Issue
Block a user