@@ -269,6 +269,7 @@ private static void ResetTypeHandlers(bool clone)
269269 lock ( typeHandlersSyncLock )
270270 {
271271 typeHandlers = [ ] ;
272+ typeHandlerFactories = [ ] ;
272273 AddTypeHandlerCore ( typeof ( DataTable ) , new DataTableHandler ( ) , clone ) ;
273274 AddTypeHandlerCore ( typeof ( XmlDocument ) , new XmlDocumentHandler ( ) , clone ) ;
274275 AddTypeHandlerCore ( typeof ( XDocument ) , new XDocumentHandler ( ) , clone ) ;
@@ -337,6 +338,26 @@ public static void RemoveTypeMap(Type type)
337338 SetTypeMap ( newCopy ) ;
338339 }
339340
341+ /// <summary>
342+ /// Register a <see cref="TypeHandlerFactory"/> that can create handlers on demand for types it claims
343+ /// via <see cref="TypeHandlerFactory.CanHandle"/>. Factories are queried in registration order when
344+ /// no direct handler is found for a type.
345+ /// </summary>
346+ /// <param name="factory">The factory to register.</param>
347+ public static void AddTypeHandlerFactory ( TypeHandlerFactory factory )
348+ {
349+ if ( factory is null ) throw new ArgumentNullException ( nameof ( factory ) ) ;
350+ lock ( typeHandlersSyncLock )
351+ {
352+ // Snapshot/mutate/swap keeps reads outside the lock lock-free.
353+ var prev = typeHandlerFactories ;
354+ var next = new TypeHandlerFactory [ prev . Length + 1 ] ;
355+ prev . CopyTo ( next , 0 ) ;
356+ next [ prev . Length ] = factory ;
357+ typeHandlerFactories = next ;
358+ }
359+ }
360+
340361 /// <summary>
341362 /// Configure the specified type to be processed by a custom handler.
342363 /// </summary>
@@ -348,7 +369,17 @@ public static void RemoveTypeMap(Type type)
348369 /// </summary>
349370 /// <param name="type">The type to handle.</param>
350371 /// <returns>Boolean value specifying whether the type will be processed by a custom handler.</returns>
351- public static bool HasTypeHandler ( Type type ) => typeHandlers . ContainsKey ( type ) ;
372+ public static bool HasTypeHandler ( Type type )
373+ {
374+ if ( typeHandlers . ContainsKey ( type ) ) return true ;
375+ // Also check registered factories; a type has a handler if any factory claims it.
376+ var factories = typeHandlerFactories ; // snapshot for thread safety
377+ foreach ( var factory in factories )
378+ {
379+ if ( factory . CanHandle ( type ) ) return true ;
380+ }
381+ return false ;
382+ }
352383
353384 /// <summary>
354385 /// Configure the specified type to be processed by a custom handler.
@@ -423,8 +454,37 @@ private static void AddTypeHandlerCore(Type type, ITypeHandler? handler, bool cl
423454 public static void AddTypeHandler < T > ( TypeHandler < T > handler ) => AddTypeHandlerCore ( typeof ( T ) , handler , true ) ;
424455
425456 private static Dictionary < Type , ITypeHandler > typeHandlers ;
457+ // Registered factories are queried (in order) when no direct entry is found in typeHandlers.
458+ // Uses a plain array with snapshot/swap so readers outside the lock never block.
459+ private static TypeHandlerFactory [ ] typeHandlerFactories = [ ] ;
426460 private static readonly object typeHandlersSyncLock = new ( ) ;
427461
462+ /// <summary>
463+ /// Looks up a registered type handler, falling back to registered <see cref="TypeHandlerFactory"/>
464+ /// instances when no direct entry is found. On a factory hit the concrete handler is instantiated,
465+ /// registered in <see cref="typeHandlers"/> and in <see cref="TypeHandlerCache{T}"/> (used by
466+ /// IL-emitted code), so subsequent lookups for the same type are O(1) dictionary reads.
467+ /// </summary>
468+ private static bool TryGetTypeHandler ( Type type , [ System . Diagnostics . CodeAnalysis . NotNullWhen ( true ) ] out ITypeHandler ? handler )
469+ {
470+ if ( typeHandlers . TryGetValue ( type , out handler ) ) return true ;
471+
472+ var factories = typeHandlerFactories ; // snapshot for thread safety
473+ foreach ( var factory in factories )
474+ {
475+ if ( factory . CanHandle ( type ) )
476+ {
477+ handler = factory . Create ( type ) ;
478+ // Cache so TypeHandlerCache<T> (used by IL-emitted code paths) is populated
479+ // and future lookups bypass factory iteration entirely.
480+ AddTypeHandlerCore ( type , handler , true ) ;
481+ return true ;
482+ }
483+ }
484+
485+ return false ;
486+ }
487+
428488 internal const string LinqBinary = "System.Data.Linq.Binary" ;
429489
430490 private const string ObsoleteInternalUsageOnly = "This method is for internal use only" ;
@@ -483,7 +543,7 @@ public static void SetDbType(IDataParameter parameter, object value)
483543 {
484544 return DbType . Binary ;
485545 }
486- if ( typeHandlers . TryGetValue ( type , out handler ) )
546+ if ( TryGetTypeHandler ( type , out handler ) )
487547 {
488548 return DbType . Object ;
489549 }
@@ -1971,7 +2031,7 @@ private static Func<DbDataReader, object> GetDeserializer(Type type, DbDataReade
19712031 else if ( ! ( type . IsEnum || type . IsArray || type . FullName == LinqBinary
19722032 || ( type . IsValueType && ( underlyingType = Nullable . GetUnderlyingType ( type ) ) is not null && underlyingType . IsEnum ) ) )
19732033 {
1974- if ( typeHandlers . TryGetValue ( type , out ITypeHandler ? handler ) )
2034+ if ( TryGetTypeHandler ( type , out ITypeHandler ? handler ) )
19752035 {
19762036 return GetHandlerDeserializer ( handler , type , startBound ) ;
19772037 }
@@ -3130,7 +3190,7 @@ private static Func<DbDataReader, object> GetSimpleValueDeserializer(Type type,
31303190 return val is DBNull ? null ! : Enum . ToObject ( effectiveType , val ) ;
31313191 } ;
31323192 }
3133- if ( typeHandlers . TryGetValue ( type , out var handler ) )
3193+ if ( TryGetTypeHandler ( type , out var handler ) )
31343194 {
31353195 return r =>
31363196 {
@@ -3192,7 +3252,7 @@ private static T Parse<T>(object? value)
31923252 }
31933253 return ( T ) Enum . ToObject ( type , value ) ;
31943254 }
3195- if ( typeHandlers . TryGetValue ( type , out ITypeHandler ? handler ) )
3255+ if ( TryGetTypeHandler ( type , out ITypeHandler ? handler ) )
31963256 {
31973257 return ( T ) handler . Parse ( type , value ) ! ;
31983258 }
@@ -3805,7 +3865,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind
38053865 {
38063866 TypeCode dataTypeCode = Type . GetTypeCode ( colType ) , unboxTypeCode = Type . GetTypeCode ( unboxType ) ;
38073867 bool hasTypeHandler ;
3808- if ( ( hasTypeHandler = typeHandlers . ContainsKey ( unboxType ) ) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type . GetTypeCode ( nullUnderlyingType ) )
3868+ if ( ( hasTypeHandler = TryGetTypeHandler ( unboxType , out _ ) ) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type . GetTypeCode ( nullUnderlyingType ) )
38093869 {
38103870 if ( hasTypeHandler )
38113871 {
0 commit comments