@@ -850,6 +850,151 @@ private static Optional<ICacheEvents<K, V>> CreateEvents(ConcurrentLruCore<K, V,
850850 return new ( new Proxy ( lru ) ) ;
851851 }
852852
853+ #if NET9_0_OR_GREATER
854+
855+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
856+ private static bool IsCompatibleKey < TAlternateKey > ( ConcurrentDictionary < K , I > d )
857+ where TAlternateKey : notnull , allows ref struct
858+ {
859+ return d . Comparer is IAlternateEqualityComparer < TAlternateKey , K > ;
860+ }
861+
862+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
863+ private static IAlternateEqualityComparer < TAlternateKey , K > GetAlternateComparer < TAlternateKey > ( ConcurrentDictionary < K , I > d )
864+ where TAlternateKey : notnull , allows ref struct
865+ {
866+ Debug . Assert ( IsCompatibleKey < TAlternateKey > ( d ) ) ;
867+ return Unsafe . As < IAlternateEqualityComparer < TAlternateKey , K > > ( d . Comparer ! ) ;
868+ }
869+
870+ public IAlternateCache < TAlternateKey , K , V > GetAlternateCache < TAlternateKey > ( ) where TAlternateKey : notnull , allows ref struct
871+ {
872+ if ( ! IsCompatibleKey < TAlternateKey > ( this . dictionary ) )
873+ {
874+ Throw . IncompatibleComparer ( ) ;
875+ }
876+
877+ return new AlternateCache < TAlternateKey > ( this ) ;
878+ }
879+
880+ public bool TryGetAlternateCache < TAlternateKey > ( [ MaybeNullWhen ( false ) ] out IAlternateCache < TAlternateKey , K , V > lookup ) where TAlternateKey : notnull , allows ref struct
881+ {
882+ if ( IsCompatibleKey < TAlternateKey > ( this . dictionary ) )
883+ {
884+ lookup = new AlternateCache < TAlternateKey > ( this ) ;
885+ return true ;
886+ }
887+
888+ lookup = default ;
889+ return false ;
890+ }
891+
892+ // Rough idea of alternate cache interface
893+ // Note: we need a sync and async variant, plumbed into ICache and IAsyncCache.
894+ public interface IAlternateCache < TAlternateKey , K , V > where TAlternateKey : notnull , allows ref struct
895+ {
896+ bool TryGet ( TAlternateKey key , [ MaybeNullWhen ( false ) ] out V value ) ;
897+
898+ bool TryRemove ( TAlternateKey key , [ MaybeNullWhen ( false ) ] out K actualKey , [ MaybeNullWhen ( false ) ] out V value ) ;
899+
900+ V GetOrAdd ( TAlternateKey altKey , Func < TAlternateKey , V > valueFactory ) ;
901+
902+ V GetOrAdd < TArg > ( TAlternateKey altKey , Func < TAlternateKey , TArg , V > valueFactory , TArg factoryArgument ) ;
903+
904+ // TryUpdate
905+ // AddOrUpdate
906+ }
907+
908+ internal readonly struct AlternateCache < TAlternateKey > : IAlternateCache < TAlternateKey , K , V > where TAlternateKey : notnull , allows ref struct
909+ {
910+ /// <summary>Initialize the instance. The dictionary must have already been verified to have a compatible comparer.</summary>
911+ internal AlternateCache ( ConcurrentLruCore < K , V , I , P , T > lru )
912+ {
913+ Debug . Assert ( lru is not null ) ;
914+ Debug . Assert ( IsCompatibleKey < TAlternateKey > ( lru . dictionary ) ) ;
915+ Lru = lru ;
916+ }
917+
918+ internal ConcurrentLruCore < K , V , I , P , T > Lru { get ; }
919+
920+ public bool TryGet ( TAlternateKey key , [ MaybeNullWhen ( false ) ] out V value )
921+ {
922+ var alternate = this . Lru . dictionary . GetAlternateLookup < TAlternateKey > ( ) ;
923+
924+ if ( alternate . TryGetValue ( key , out var item ) )
925+ {
926+ return Lru . GetOrDiscard ( item , out value ) ;
927+ }
928+
929+ value = default ;
930+ Lru . telemetryPolicy . IncrementMiss ( ) ;
931+ return false ;
932+ }
933+
934+ public bool TryRemove ( TAlternateKey key , [ MaybeNullWhen ( false ) ] out K actualKey , [ MaybeNullWhen ( false ) ] out V value )
935+ {
936+ var alternate = this . Lru . dictionary . GetAlternateLookup < TAlternateKey > ( ) ;
937+
938+ if ( alternate . TryGetValue ( key , out var item ) )
939+ {
940+ Lru . OnRemove ( item . Key , item , ItemRemovedReason . Removed ) ;
941+ actualKey = item . Key ;
942+ value = item . Value ;
943+ return true ;
944+ }
945+
946+ actualKey = default ;
947+ value = default ;
948+ return false ;
949+ }
950+
951+ public V GetOrAdd ( TAlternateKey altKey , Func < TAlternateKey , V > valueFactory )
952+ {
953+ var alternate = this . Lru . dictionary . GetAlternateLookup < TAlternateKey > ( ) ;
954+
955+ while ( true )
956+ {
957+ if ( alternate . TryGetValue ( altKey , out var item ) )
958+ {
959+ return item . Value ;
960+ }
961+
962+ // We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add.
963+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
964+ K key = GetAlternateComparer < TAlternateKey > ( this . Lru . dictionary ) . Create ( altKey ) ;
965+ V value = valueFactory ( altKey ) ;
966+ if ( Lru . TryAdd ( key , value ) )
967+ {
968+ return value ;
969+ }
970+ }
971+ }
972+
973+ public V GetOrAdd < TArg > ( TAlternateKey altKey , Func < TAlternateKey , TArg , V > valueFactory , TArg factoryArgument )
974+ {
975+ var alternate = this . Lru . dictionary . GetAlternateLookup < TAlternateKey > ( ) ;
976+
977+ while ( true )
978+ {
979+ if ( alternate . TryGetValue ( altKey , out var item ) )
980+ {
981+ return item . Value ;
982+ }
983+
984+ // We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add.
985+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
986+ K key = GetAlternateComparer < TAlternateKey > ( this . Lru . dictionary ) . Create ( altKey ) ;
987+ V value = valueFactory ( altKey , factoryArgument ) ;
988+ if ( Lru . TryAdd ( key , value ) )
989+ {
990+ return value ;
991+ }
992+ }
993+ }
994+ }
995+
996+ #endif
997+
853998 // To get JIT optimizations, policies must be structs.
854999 // If the structs are returned directly via properties, they will be copied. Since
8551000 // telemetryPolicy is a mutable struct, copy is bad. One workaround is to store the
0 commit comments