diff --git a/AyCode.Core/Serializers/IdentityMap.cs b/AyCode.Core/Serializers/IdentityMap.cs index 68256cf..5b6eddb 100644 --- a/AyCode.Core/Serializers/IdentityMap.cs +++ b/AyCode.Core/Serializers/IdentityMap.cs @@ -43,35 +43,35 @@ public interface IIdentityMap /// - Large keys: custom hash table with chaining /// No Dictionary overhead, no per-entry allocation. /// -/// The ID type (int, long, Guid, string) -public sealed class IdentityMap : IIdentityMap where TId : notnull +/// The key type (int, long, Guid, string) +/// The value type +public sealed class IdentityMap : IIdentityMap where TKey : notnull { - private const bool _useSmallInt = true; + private const bool _useSmallInt = false; - // Small int optimization (TId = int only, 0-4095), 512 bytes (fits in L1 cache!) + // Small int optimization (TKey = int only, 0-4095), 512 bytes (fits in L1 cache!) private const int SmallBitmapSize = 64; private const int SmallSize = 4096; - // Small int optimization (TId = int only, 0-65535) + // Small int optimization (TKey = int only, 0-65535) //private const int SmallBitmapSize = 1024; //private const int SmallSize = SmallBitmapSize * 64; // Slot for hash table entries (generation needed for hash table validity) private struct HashSlot { - public object? Value; + public TValue Value; public int Next; // next slot index in chain (-1 = end) } - // Small int storage - SIMPLE: bitmap for "seen?" + direct object[] for values + // Small int storage - SIMPLE: bitmap for "seen?" + direct TValue[] for values // Bitmap protects values - no generation needed, no value array clearing on Reset! private ulong[]? _smallBitmap; // Cache-friendly "seen?" check (512 bytes, cleared on Reset) - private object?[]? _smallValues; // Direct value storage (NOT cleared on Reset - bitmap protects) // Hash table storage (for large ints and other types) private int[]? _buckets; // bucket index → first entry index private HashSlot[]? _entries; // hash table entries - private TId[]? _keys; // keys for equality check + private TKey[]? _keys; // keys for equality check private int _count; // number of entries in hash table private int _bucketsLength; // actual rented length (for modulo) private int _entriesLength; // actual capacity @@ -79,27 +79,28 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull private const int InitialHashCapacity = 16; // Type checks (JIT eliminates these at compile time) - private static readonly bool IsInt32 = typeof(TId) == typeof(int); - private static readonly bool IsInt64 = typeof(TId) == typeof(long); - private static readonly bool IsGuid = typeof(TId) == typeof(Guid); - private static readonly bool IsString = typeof(TId) == typeof(string); + private static readonly bool IsInt32 = typeof(TKey) == typeof(int); + private static readonly bool IsInt64 = typeof(TKey) == typeof(long); + private static readonly bool IsGuid = typeof(TKey) == typeof(Guid); + private static readonly bool IsString = typeof(TKey) == typeof(string); + private static readonly bool IsValueTypeValue = typeof(TValue).IsValueType; public IdentityMap() { } - + /// /// Tries to add a key to tracking (serialization). /// Returns true if first occurrence (key was added). /// Returns false if already seen. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryAddKey(TId key) + public bool TryAddKey(TKey key) { // Small int fast path if (_useSmallInt && IsInt32) { - var intKey = Unsafe.As(ref key); + var intKey = Unsafe.As(ref key); if ((uint)intKey < SmallSize) { return TryAddSmallInt(intKey); @@ -113,12 +114,11 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryAddSmallInt(int key) { - // Lazy init - ONLY bitmap for tracking (serializer doesn't need _smallValues) + // Lazy init - ONLY bitmap for tracking if (_smallBitmap == null) { _smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize); Array.Clear(_smallBitmap, 0, SmallBitmapSize); - // _smallValues allocated lazily in TryGetOrAddSmallIntValue when needed } var wordIdx = key >> 6; @@ -133,17 +133,17 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool KeyEquals(TId a, TId b) + private static bool KeyEquals(TKey a, TKey b) { - // JIT eliminates these branches at compile time for each TId instantiation + // JIT eliminates these branches at compile time for each TKey instantiation if (IsInt32) { - return Unsafe.As(ref a) == Unsafe.As(ref b); + return Unsafe.As(ref a) == Unsafe.As(ref b); } if (IsString) { - var strA = Unsafe.As(ref a); - var strB = Unsafe.As(ref b); + var strA = Unsafe.As(ref a); + var strB = Unsafe.As(ref b); // Fast path: reference equality (interned strings) //if (ReferenceEquals(strA, strB)) return true; @@ -153,18 +153,18 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } if (IsInt64) { - return Unsafe.As(ref a) == Unsafe.As(ref b); + return Unsafe.As(ref a) == Unsafe.As(ref b); } if (IsGuid) { - return Unsafe.As(ref a) == Unsafe.As(ref b); + return Unsafe.As(ref a) == Unsafe.As(ref b); } // Fallback for other types - return EqualityComparer.Default.Equals(a, b); + return EqualityComparer.Default.Equals(a, b); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryAddHash(TId key, out int slotIndex) + private bool TryAddHash(TKey key, out int slotIndex) { var hash = GetHashCode(key); @@ -179,8 +179,8 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull // Search chain for (var i = _buckets[bucketIdx]; i >= 0; i = _entries![i].Next) { - //if (EqualityComparer.Default.Equals(_keys![i], key)) - if (KeyEquals(_keys![i], key)) // Direct comparison, no virtual call + //if (EqualityComparer.Default.Equals(_keys![i], key)) + if (KeyEquals(_keys![i], key)) // Direct comparison, no virtual call { slotIndex = i; return false; // already seen @@ -203,13 +203,13 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetHashCode(TId key) + private static int GetHashCode(TKey key) { // Specialized hash for known types - JIT eliminates branches - if (IsInt32) return Unsafe.As(ref key); - if (IsInt64) return Unsafe.As(ref key).GetHashCode(); - if (IsGuid) return Unsafe.As(ref key).GetHashCode(); - if (IsString) return string.GetHashCode(Unsafe.As(ref key), StringComparison.Ordinal); + if (IsInt32) return Unsafe.As(ref key); + if (IsInt64) return Unsafe.As(ref key).GetHashCode(); + if (IsGuid) return Unsafe.As(ref key).GetHashCode(); + if (IsString) return string.GetHashCode(Unsafe.As(ref key), StringComparison.Ordinal); return key.GetHashCode(); } @@ -218,16 +218,16 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull { // Use remembered capacity if larger (from previous serialization) var actualCapacity = Math.Max(capacity, _bucketsLength); - + _buckets = ArrayPool.Shared.Rent(actualCapacity); - _bucketsLength = actualCapacity; - Array.Fill(_buckets, -1, 0, actualCapacity); + _bucketsLength = _buckets.Length; + Array.Fill(_buckets, -1, 0, _bucketsLength); _entries = ArrayPool.Shared.Rent(actualCapacity); - _entriesLength = actualCapacity; + _entriesLength = _entries.Length; //Array.Clear(_entries, 0, _entriesLength); - _keys = ArrayPool.Shared.Rent(actualCapacity); + _keys = ArrayPool.Shared.Rent(actualCapacity); //Array.Clear(_keys, 0, actualCapacity); _count = 0; @@ -239,9 +239,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull // Rent new arrays var newBuckets = ArrayPool.Shared.Rent(newCapacity); - Array.Fill(newBuckets, -1, 0, newCapacity); // MUST fill with -1 + var newBucketsLength = newBuckets.Length; + Array.Fill(newBuckets, -1, 0, newBucketsLength); var newEntries = ArrayPool.Shared.Rent(newCapacity); - var newKeys = ArrayPool.Shared.Rent(newCapacity); + var newKeys = ArrayPool.Shared.Rent(newCapacity); // Copy entries (no clear needed) Array.Copy(_entries!, newEntries, _count); @@ -250,7 +251,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull // Rebuild bucket chains for (var i = 0; i < _count; i++) { - var bucketIdx = (GetHashCode(newKeys[i]) & 0x7FFFFFFF) % newCapacity; + var bucketIdx = (GetHashCode(newKeys[i]) & 0x7FFFFFFF) % newBucketsLength; newEntries[i].Next = newBuckets[bucketIdx]; newBuckets[bucketIdx] = i; } @@ -258,22 +259,22 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull // Return old arrays to pool ArrayPool.Shared.Return(_buckets!); ArrayPool.Shared.Return(_entries!); - ArrayPool.Shared.Return(_keys!); + ArrayPool.Shared.Return(_keys!); _buckets = newBuckets; - _bucketsLength = newCapacity; + _bucketsLength = newBucketsLength; _entries = newEntries; - _entriesLength = newCapacity; + _entriesLength = newEntries.Length; _keys = newKeys; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasKey(TId key) + public bool HasKey(TKey key) { // Small int fast path - bitmap check is cache-friendly (512 bytes) if (_useSmallInt && IsInt32) { - var intKey = Unsafe.As(ref key); + var intKey = Unsafe.As(ref key); if ((uint)intKey < SmallSize && _smallBitmap != null) { var wordIdx = intKey >> 6; @@ -298,87 +299,28 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull /// /// Checks if key exists and returns existing value, or adds new key (deserialization). - /// Returns true if first occurrence (key was added, out = null). + /// Returns true if first occurrence (key was added, out = default). /// Returns false if already seen (out = existing value). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetOrAddKey(TId key, out object? existing) + public bool TryGetOrAddKey(TKey key, out TValue? existing) { - // NOTE: SmallInt path DISABLED for deserializer. - // The 512KB _smallValues sparse array causes cache misses. - // Hash table has better cache locality. - - //// Small int fast path - //if (_useSmallInt && IsInt32) - //{ - // var intKey = Unsafe.As(ref key); - // if ((uint)intKey < SmallSize) - // { - // return TryGetOrAddSmallInt(intKey, out existing); - // } - //} - - // Hash table path if (!TryAddHash(key, out var slotIndex)) { existing = _entries![slotIndex].Value; return false; } - existing = null; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryGetOrAddSmallInt(int key, out object? existing) - { - // Lazy init bitmap only - NOT _smallValues! - // This method is for tracking (key seen?), not value storage. - if (_smallBitmap == null) - { - _smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize); - Array.Clear(_smallBitmap, 0, SmallBitmapSize); - } - - var wordIdx = key >> 6; - var bit = 1UL << (key & 63); - - ref var word = ref _smallBitmap[wordIdx]; - if ((word & bit) != 0) - { - // Bitmap says seen - return stored value if any - existing = _smallValues?[key]; - return false; // Already seen - } - - // First occurrence - mark as seen (don't touch _smallValues!) - word |= bit; - existing = null; + existing = default; return true; } /// - /// Gets existing value or adds new value for key (deserialization with object storage). + /// Gets existing value or adds new value for key (deserialization with value storage). /// Returns the existing value if key was seen before, or stores and returns newValue. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public object TryGetOrAddValue(TId key, object newValue) + public TValue TryGetOrAddValue(TKey key, TValue newValue) { - // NOTE: SmallInt path DISABLED for deserializer value storage. - // The 512KB _smallValues sparse array causes cache misses. - // Hash table has better cache locality (only stores actual entries). - // Bitmap is only useful for tracking (serializer) - not for value storage. - - //// Small int fast path - //if (_useSmallInt && IsInt32) - //{ - // var intKey = Unsafe.As(ref key); - // if ((uint)intKey < SmallSize) - // { - // return TryGetOrAddSmallIntValue(intKey, newValue); - // } - //} - - // Hash table path if (!TryAddHash(key, out var slotIndex)) { var existing = _entries![slotIndex].Value; @@ -388,71 +330,16 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull return newValue; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object TryGetOrAddSmallIntValue(int key, object newValue) - { - // Lazy init bitmap - if (_smallBitmap == null) - { - _smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize); - Array.Clear(_smallBitmap, 0, SmallBitmapSize); - } - - // Lazy init values array (only when needed for deserialization) - if (_smallValues == null) - { - _smallValues = ArrayPool.Shared.Rent(SmallSize); - Array.Clear(_smallValues, 0, SmallSize); - } - - var wordIdx = key >> 6; - var bit = 1UL << (key & 63); - - ref var word = ref _smallBitmap[wordIdx]; - if ((word & bit) != 0) - { - // Bitmap says seen - check if value exists - var existing = _smallValues![key]; - if (existing != null) - return existing; - } - - // First occurrence or no value yet - store new value - word |= bit; - _smallValues![key] = newValue; - return newValue; - } - /// - /// Tries to get the value for a key (ObjectRef lookup). + /// Tries to get the value for a key. /// Returns true if found, false if not. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TId key, out object? value) + public bool TryGetValue(TKey key, out TValue? value) { - //// Small int fast path - bitmap check first (cache-friendly, 512 bytes) - //if (_useSmallInt && IsInt32) - //{ - // var intKey = Unsafe.As(ref key); - // if ((uint)intKey < SmallSize && _smallBitmap != null) - // { - // var wordIdx = intKey >> 6; - // var bit = 1UL << (intKey & 63); - // if ((_smallBitmap[wordIdx] & bit) != 0) - // { - // // Bitmap says seen - return value if exists - // value = _smallValues?[intKey]; - // return value != null; - // } - // value = null; - // return false; - // } - //} - - // Hash table path if (_buckets == null) { - value = null; + value = default; return false; } @@ -467,10 +354,20 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull return true; } } - value = null; + value = default; return false; } + /// + /// Returns a reference to the value at the given slot index. + /// Use with slotIndex from TryAddHash for in-place value modification. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue GetValueRef(int slotIndex) + { + return ref _entries![slotIndex].Value; + } + /// /// Resets the identity map for reuse. /// Small arrays (≤ InitialHashCapacity*5): keep and clear (faster than pool round-trip). @@ -479,29 +376,6 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull /// If true, pre-rent arrays at next capacity (useful for async Clear to shift work from hot path) public void Reset(bool preRentBuckets = false) { - if (_smallValues != null) - { - //Array.Clear(_smallValues, 0, SmallSize); - - // Végigmegyünk a bitmap-en és csak a set bit-ekhez tartozó értékeket töröljük - for (var wordIdx = 0; wordIdx < SmallBitmapSize; wordIdx++) - { - var word = _smallBitmap[wordIdx]; - if (word == 0) continue; - - var baseIdx = wordIdx << 6; - while (word != 0) - { - var bitPos = BitOperations.TrailingZeroCount(word); - _smallValues[baseIdx + bitPos] = null; - word &= word - 1; // Clear lowest set bit - } - } - - //ArrayPool.Shared.Return(_smallValues, clearArray: true); // Clear to release object refs - //_smallValues = null; - } - if (_smallBitmap != null) { Array.Clear(_smallBitmap, 0, SmallBitmapSize); @@ -524,7 +398,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull _count = 0; return; } - + // Large arrays: return to pool, remember half capacity var nextCapacity = Math.Max(_bucketsLength / 2, InitialHashCapacity * 5); @@ -538,20 +412,20 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull ArrayPool.Shared.Return(_buckets); ArrayPool.Shared.Return(_entries!,false); - ArrayPool.Shared.Return(_keys!, false); - + ArrayPool.Shared.Return(_keys!, false); + if (preRentBuckets) { // Pre-rent arrays now (async background) so Pool.Get() is faster _buckets = ArrayPool.Shared.Rent(nextCapacity); - _bucketsLength = nextCapacity; - Array.Fill(_buckets, -1, 0, nextCapacity); - + _bucketsLength = _buckets.Length; + Array.Fill(_buckets, -1, 0, _bucketsLength); + _entries = ArrayPool.Shared.Rent(nextCapacity); - _entriesLength = nextCapacity; + _entriesLength = _entries.Length; //Array.Clear(_entries, 0, _entriesLength); - - _keys = ArrayPool.Shared.Rent(nextCapacity); + + _keys = ArrayPool.Shared.Rent(nextCapacity); //Array.Clear(_keys, 0, nextCapacity); } else @@ -568,108 +442,3 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } #endregion - - -//#region IId Reference Tracking - -///// -///// Specifies the accessor type for IId.Id property to enable typed getter dispatch without boxing. -///// -//public enum IdAccessorType : byte -//{ -// None = 0, -// /// Id is int (most common). -// Int32 = 1, -// /// Id is long. -// Int64 = 2, -// /// Id is Guid. -// Guid = 3, -//} - -///// -///// Interface for identity maps used in serialization tracking. -///// Enables type-safe Reset() without knowing the generic type parameter. -///// -//public interface IIdentityMap -//{ -// /// -// /// Resets the identity map for reuse between serializations. -// /// -// void Reset(); -//} - -///// -///// Generic identity map for tracking IId values during serialization/deserialization. -///// Uses Dictionary internally for unified tracking + object storage. -///// -///// The ID type (int, long, Guid) -//public sealed class IdentityMap : IIdentityMap where TId : notnull -//{ -// private readonly Dictionary _tracked; - -// public IdentityMap() -// { -// _tracked = new Dictionary(EqualityComparer.Default); -// } - -// /// -// /// Tries to add a key to tracking (serialization). -// /// Returns true if first occurrence (key was added). -// /// Returns false if already seen. -// /// -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public bool TryAddKey(TId key) -// { -// return _tracked.TryAdd(key, null); -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public bool HasKey(TId key) => _tracked.ContainsKey(key); - - -// /// -// /// Checks if key exists and returns existing value, or adds new key (deserialization). -// /// Returns true if first occurrence (key was added, out = null). -// /// Returns false if already seen (out = existing value). -// /// -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public bool TryGetOrAddKey(TId key, out object? existing) -// { -// ref var slot = ref CollectionsMarshal.GetValueRefOrAddDefault(_tracked, key, out var exists); -// existing = exists ? slot : null; -// return !exists; -// } - -// /// -// /// Gets existing value or adds new value for key (deserialization with object storage). -// /// Returns the existing value if key was seen before, or stores and returns newValue. -// /// -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public object TryGetOrAddValue(TId key, object newValue) -// { -// ref var slot = ref CollectionsMarshal.GetValueRefOrAddDefault(_tracked, key, out var exists); -// if (exists && slot != null) return slot; -// slot = newValue; -// return newValue; -// } - -// /// -// /// Tries to get the value for a key (ObjectRef lookup). -// /// Returns true if found, false if not. -// /// -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public bool TryGetValue(TId key, out object? value) -// { -// return _tracked.TryGetValue(key, out value); -// } - -// /// -// /// Resets the identity map for reuse. -// /// -// public void Reset() -// { -// _tracked.Clear(); -// } -//} - -//#endregion \ No newline at end of file diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs index 04b714b..65153f9 100644 --- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs +++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs @@ -40,19 +40,18 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat /// /// Typed IdentityMap for Int32 IDs. Direct access, no type check. /// - internal IdentityMap? IdentityMapInt32; + internal IdentityMap? IdentityMapInt32; /// /// Typed IdentityMap for Int64 IDs. Direct access, no type check. /// - internal IdentityMap? IdentityMapInt64; + internal IdentityMap? IdentityMapInt64; /// /// Typed IdentityMap for Guid IDs. Direct access, no type check. /// - internal IdentityMap? IdentityMapGuid; - - + internal IdentityMap? IdentityMapGuid; + #endregion /// @@ -152,7 +151,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat { if (id == 0) return newObj; // Default Id - no tracking - var map = IdentityMapInt32 ??= new IdentityMap(); + var map = IdentityMapInt32 ??= new IdentityMap(); return map.TryGetOrAddValue(id, newObj); } @@ -166,7 +165,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat var id = RefIdGetterInt32!(instance); if (id == 0) return; - var map = IdentityMapInt32 ??= new IdentityMap(); + var map = IdentityMapInt32 ??= new IdentityMap(); map.TryGetOrAddValue(id, instance); } @@ -189,7 +188,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat { if (id == 0) return newObj; - var map = IdentityMapInt64 ??= new IdentityMap(); + var map = IdentityMapInt64 ??= new IdentityMap(); return map.TryGetOrAddValue(id, newObj); } @@ -199,7 +198,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat var id = RefIdGetterInt64!(instance); if (id == 0) return; - var map = IdentityMapInt64 ??= new IdentityMap(); + var map = IdentityMapInt64 ??= new IdentityMap(); map.TryGetOrAddValue(id, instance); } @@ -222,7 +221,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat { if (id == Guid.Empty) return newObj; - var map = IdentityMapGuid ??= new IdentityMap(); + var map = IdentityMapGuid ??= new IdentityMap(); return map.TryGetOrAddValue(id, newObj); } @@ -232,7 +231,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat var id = RefIdGetterGuid!(instance); if (id == Guid.Empty) return; - var map = IdentityMapGuid ??= new IdentityMap(); + var map = IdentityMapGuid ??= new IdentityMap(); map.TryGetOrAddValue(id, instance); } @@ -245,25 +244,25 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat /// SLOW PATH - use typed methods (TryGetValueInt32, etc.) instead! /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IdentityMap GetOrCreateIdentityMap() where TId : notnull + public IdentityMap GetOrCreateIdentityMap() where TId : notnull { // Route to typed fields based on TId if (typeof(TId) == typeof(int)) { - var map = IdentityMapInt32 ??= new IdentityMap(); - return Unsafe.As, IdentityMap>(ref map); + var map = IdentityMapInt32 ??= new IdentityMap(); + return Unsafe.As, IdentityMap>(ref map); } if (typeof(TId) == typeof(long)) { - var map = IdentityMapInt64 ??= new IdentityMap(); - return Unsafe.As, IdentityMap>(ref map); + var map = IdentityMapInt64 ??= new IdentityMap(); + return Unsafe.As, IdentityMap>(ref map); } if (typeof(TId) == typeof(Guid)) { - var map = IdentityMapGuid ??= new IdentityMap(); - return Unsafe.As, IdentityMap>(ref map); + var map = IdentityMapGuid ??= new IdentityMap(); + return Unsafe.As, IdentityMap>(ref map); } - + throw new NotSupportedException($"Id type {typeof(TId)} is not supported"); }