@@ -156,6 +156,7 @@ public static void DeserializeRefBoxed(ref object val, ref Reader reader, Type t
156156 [ SuppressMessage ( "ReSharper" , "StaticMemberInGenericType" ) ]
157157 public static class CachedDeserializer < T >
158158 {
159+ private static int _typeId = - 1 ;
159160 private static DeserializeDelegate < T > _deserializer ;
160161 private static DeserializeDelegateRef < T > _deserializerRef ;
161162 private static readonly FastMap < int , DeserializeDelegate < T > > SubTypeDeserializers = new ( ) ;
@@ -184,6 +185,7 @@ public static class CachedDeserializer<T>
184185 public static void SetDeserializer ( int typeId , DeserializeDelegate < T > deserializer ,
185186 DeserializeDelegateRef < T > deserializerRef )
186187 {
188+ _typeId = typeId ;
187189 _deserializer = deserializer ;
188190 _deserializerRef = deserializerRef ;
189191 SubTypeDeserializers . Add ( typeId , _deserializer ) ;
@@ -192,24 +194,40 @@ public static void SetDeserializer(int typeId, DeserializeDelegate<T> deserializ
192194
193195 public static void AddSubTypeDeserializer < TSub > ( int subTypeId ,
194196 DeserializeDelegate < TSub > deserializer ,
195- DeserializeDelegateRef < TSub > deserializerRef )
197+ DeserializeDelegateRef < TSub > deserializerRef ) where TSub : T
196198 {
197- SubTypeDeserializers . Add ( subTypeId , ( out T value , ref Reader reader ) =>
199+ // Use static generic helper classes to create inlineable wrappers
200+ SubTypeDeserializerWrapper < TSub > . OutDeserializer = deserializer ;
201+ SubTypeDeserializerWrapper < TSub > . RefDeserializer = deserializerRef ;
202+ SubTypeDeserializers . Add ( subTypeId , SubTypeDeserializerWrapper < TSub > . DeserializeOutWrapper ) ;
203+ SubTypeDeserializerRefs . Add ( subTypeId , SubTypeDeserializerWrapper < TSub > . DeserializeRefWrapper ) ;
204+ }
205+
206+ // Static wrapper class per TSub - allows better inlining than lambda
207+ private static class SubTypeDeserializerWrapper < TSub > where TSub : T
208+ {
209+ public static DeserializeDelegate < TSub > OutDeserializer ;
210+ public static DeserializeDelegateRef < TSub > RefDeserializer ;
211+
212+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
213+ public static void DeserializeOutWrapper ( out T value , ref Reader reader )
198214 {
199- deserializer ( out TSub subValue , ref reader ) ;
200- value = subValue is T val ? val : default ;
201- } ) ;
202- SubTypeDeserializerRefs . Add ( subTypeId , ( ref T value , ref Reader reader ) =>
215+ OutDeserializer ( out TSub subValue , ref reader ) ;
216+ value = subValue ;
217+ }
218+
219+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
220+ public static void DeserializeRefWrapper ( ref T value , ref Reader reader )
203221 {
204222 if ( value is TSub val )
205223 {
206- deserializerRef ( ref val , ref reader ) ;
224+ RefDeserializer ( ref val , ref reader ) ;
207225 }
208226 else
209227 {
210- throw new Exception ( $ "Cannot cast { value . GetType ( ) . FullName } to { typeof ( T ) . FullName } " ) ;
228+ ThrowInvalidCast ( value ? . GetType ( ) ) ;
211229 }
212- } ) ;
230+ }
213231 }
214232
215233 // ULTRA-OPTIMIZED: Single boxed method with aggressive inlining and branch elimination
@@ -309,6 +327,13 @@ private static void DeserializePolymorphic(out T value, ref Reader reader)
309327 return ;
310328 }
311329
330+ // Fast path: exact type match
331+ if ( typeId == _typeId )
332+ {
333+ _deserializer ( out value , ref reader ) ;
334+ return ;
335+ }
336+
312337 // Fast path: inline cache hit (most common case for batched data)
313338 if ( typeId == _cachedTypeIdOut )
314339 {
@@ -317,17 +342,10 @@ private static void DeserializePolymorphic(out T value, ref Reader reader)
317342 }
318343
319344 // Slow path: lookup and update cache
320- DeserializePolymorphicSlow ( out value , ref reader , typeId ) ;
321- }
322-
323- [ MethodImpl ( MethodImplOptions . NoInlining ) ]
324- private static void DeserializePolymorphicSlow ( out T value , ref Reader reader , int typeId )
325- {
326345 // Handle subtype with single lookup
327346 if ( SubTypeDeserializers . TryGetValue ( typeId , out var subTypeDeserializer ) )
328347 {
329- _cachedTypeIdOut = typeId ;
330- _cachedDeserializer = subTypeDeserializer ;
348+ ( _cachedTypeIdOut , _cachedDeserializer ) = ( typeId , subTypeDeserializer ) ;
331349 subTypeDeserializer ( out value , ref reader ) ;
332350 return ;
333351 }
@@ -348,6 +366,13 @@ private static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
348366 return ;
349367 }
350368
369+ // Fast path: exact type match
370+ if ( typeId == _typeId )
371+ {
372+ _deserializerRef ( ref value , ref reader ) ;
373+ return ;
374+ }
375+
351376 // Fast path: inline cache hit (most common case for batched data)
352377 if ( typeId == _cachedTypeIdRef )
353378 {
@@ -356,17 +381,10 @@ private static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
356381 }
357382
358383 // Slow path: lookup and update cache
359- DeserializeRefPolymorphicSlow ( ref value , ref reader , typeId ) ;
360- }
361-
362- [ MethodImpl ( MethodImplOptions . NoInlining ) ]
363- private static void DeserializeRefPolymorphicSlow ( ref T value , ref Reader reader , int typeId )
364- {
365384 // Handle subtype deserialization
366385 if ( SubTypeDeserializerRefs . TryGetValue ( typeId , out var subTypeDeserializer ) )
367386 {
368- _cachedTypeIdRef = typeId ;
369- _cachedDeserializerRef = subTypeDeserializer ;
387+ ( _cachedTypeIdRef , _cachedDeserializerRef ) = ( typeId , subTypeDeserializer ) ;
370388 subTypeDeserializer ( ref value , ref reader ) ;
371389 return ;
372390 }
0 commit comments