From 26f2920f33a5e53688b474d6a7bc6a58ccf652bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 17 Jul 2025 15:37:04 +0200 Subject: [PATCH] Enhance HashSet AddRange with typed source detection --- .../RecyclableHashSet.AddRange.cs | 220 +++++++++++++++--- 1 file changed, 185 insertions(+), 35 deletions(-) diff --git a/Recyclable.Collections/RecyclableHashSet.AddRange.cs b/Recyclable.Collections/RecyclableHashSet.AddRange.cs index 707bb07..e413bf0 100644 --- a/Recyclable.Collections/RecyclableHashSet.AddRange.cs +++ b/Recyclable.Collections/RecyclableHashSet.AddRange.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Numerics; @@ -10,6 +12,96 @@ namespace Recyclable.Collections { internal static class zRecyclableHashSetAddRange { + private static class AddRangeHelper + { + private static readonly Action, object>? _dictionaryAdder; + private static readonly Action, object>? _sortedListAdder; + private static readonly Action, object>? _sortedDictionaryAdder; + private static readonly Type? _dictionaryType; + private static readonly Type? _sortedListType; + private static readonly Type? _sortedDictionaryType; + + static AddRangeHelper() + { + Type elementType = typeof(T); + if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args); + _sortedDictionaryType = typeof(RecyclableSortedDictionary<,>).MakeGenericType(args); + + MethodInfo? method = typeof(zRecyclableHashSetAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableHashSet<>).MakeGenericType(elementType), _dictionaryType }, + modifiers: null); + if (method != null) + { + var setParam = Expression.Parameter(typeof(RecyclableHashSet)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, setParam, Expression.Convert(objParam, _dictionaryType)); + _dictionaryAdder = Expression.Lambda, object>>(call, setParam, objParam).Compile(); + } + + method = typeof(zRecyclableHashSetAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableHashSet<>).MakeGenericType(elementType), _sortedDictionaryType }, + modifiers: null); + if (method != null) + { + var setParam = Expression.Parameter(typeof(RecyclableHashSet)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, setParam, Expression.Convert(objParam, _sortedDictionaryType)); + _sortedDictionaryAdder = Expression.Lambda, object>>(call, setParam, objParam).Compile(); + } + } + else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)) + { + Type[] args = elementType.GetGenericArguments(); + _sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args); + MethodInfo? method = typeof(zRecyclableHashSetAddRange).GetMethod( + nameof(AddRange), + BindingFlags.NonPublic | BindingFlags.Static, + binder: null, + types: new[] { typeof(RecyclableHashSet<>).MakeGenericType(elementType), _sortedListType }, + modifiers: null); + if (method != null) + { + var setParam = Expression.Parameter(typeof(RecyclableHashSet)); + var objParam = Expression.Parameter(typeof(object)); + var call = Expression.Call(method, setParam, Expression.Convert(objParam, _sortedListType)); + _sortedListAdder = Expression.Lambda, object>>(call, setParam, objParam).Compile(); + } + } + } + + internal static bool TryAddRange(RecyclableHashSet set, IEnumerable items) + { + if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items)) + { + _dictionaryAdder(set, items); + return true; + } + + if (_sortedDictionaryAdder != null && _sortedDictionaryType!.IsInstanceOfType(items)) + { + _sortedDictionaryAdder(set, items); + return true; + } + + if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items)) + { + _sortedListAdder(set, items); + return true; + } + + return false; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ref RecyclableHashSet.Entry GetEntry(RecyclableHashSet set, int index) where T : notnull @@ -1434,48 +1526,106 @@ namespace Recyclable.Collections internal static void AddRange(this RecyclableHashSet set, IEnumerable items) where T : notnull { - foreach (T value in items) + if (items is RecyclableList recyclableList) { - int requiredCount = set._count + 1; - if (requiredCount > set._entries.Length * set._blockSize) + AddRange(set, recyclableList); + } + else if (items is RecyclableLongList recyclableLongList) + { + AddRange(set, recyclableLongList); + } + else if (items is T[] array) + { + AddRange(set, array); + } + else if (items is List list) + { + AddRange(set, list); + } + else if (items is ICollection iCollection) + { + AddRange(set, iCollection); + } + else if (items is ICollection collection) + { + AddRange(set, collection); + } + else if (items is RecyclableHashSet hashSet) + { + AddRange(set, hashSet); + } + else if (items is RecyclableStack stack) + { + AddRange(set, stack); + } + else if (items is RecyclableSortedSet sortedSet) + { + AddRange(set, sortedSet); + } + else if (items is RecyclableLinkedList linkedList) + { + AddRange(set, linkedList); + } + else if (items is RecyclablePriorityQueue priorityQueue) + { + AddRange(set, priorityQueue); + } + else if (items is RecyclableQueue queue) + { + AddRange(set, queue); + } + else if (AddRangeHelper.TryAddRange(set, items)) + { + } + else if (items is IReadOnlyList readOnlyList) + { + AddRange(set, readOnlyList); + } + else + { + foreach (T value in items) { - EnsureCapacity(set, requiredCount); - } - - if (set._count >= (set._buckets.Length * 3) / 4) - { - ResizeBuckets(set, set._buckets.Length << 1); - } - - int hash = value?.GetHashCode() & int.MaxValue ?? 0; - int bucketMask = set._buckets.Length - 1; - int bucket = hash & bucketMask; - - int index = set._buckets[bucket]; - bool exists = false; - while (index >= 0) - { - ref var check = ref GetEntry(set, index); - if (check.HashCode == hash && EqualityComparer.Default.Equals(check.Value, value)) + int requiredCount = set._count + 1; + if (requiredCount > set._entries.Length * set._blockSize) { - exists = true; - break; + EnsureCapacity(set, requiredCount); } - index = check.Next; - } + if (set._count >= (set._buckets.Length * 3) / 4) + { + ResizeBuckets(set, set._buckets.Length << 1); + } - if (exists) - { - continue; - } + int hash = value?.GetHashCode() & int.MaxValue ?? 0; + int bucketMask = set._buckets.Length - 1; + int bucket = hash & bucketMask; - int insertIndex = set._count++; - ref var entry = ref GetEntry(set, insertIndex); - entry.HashCode = hash; - entry.Value = value!; - entry.Next = set._buckets[bucket]; - set._buckets[bucket] = insertIndex; + int index = set._buckets[bucket]; + bool exists = false; + while (index >= 0) + { + ref var check = ref GetEntry(set, index); + if (check.HashCode == hash && EqualityComparer.Default.Equals(check.Value, value)) + { + exists = true; + break; + } + + index = check.Next; + } + + if (exists) + { + continue; + } + + int insertIndex = set._count++; + ref var entry = ref GetEntry(set, insertIndex); + entry.HashCode = hash; + entry.Value = value!; + entry.Next = set._buckets[bucket]; + set._buckets[bucket] = insertIndex; + } } } }