Extract net9 alternate-key cache support for ConcurrentLru#739
Extract net9 alternate-key cache support for ConcurrentLru#739
Conversation
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/766875ac-21b6-44fa-a3cb-3b38b9beed94
|
@copilot similar to |
|
@copilot extract the interface |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/ccdc20c6-3b27-4e41-b068-265e05d1dc33
|
@copilot in the positive unit tests. use |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/04f2a992-fbe7-4afa-83a1-cb7a7759a43b
Updated in 51324ab — the positive tests now use |
|
@copilot implement this feedback: go with "AlternateLookup" naming as opposed to "AlternateCache" as it is an adapter providing an alternative lookup over an existing cache, not an alternative cache as such. Also, "AlernativeLookup" is the established name in the .NET ecosystem. Also, the alternate lookup on the underlying ConcurrentDictionary<K, V> should be retrieved and cached in a private field of the AlternateLookup struct, as opposed to calling this.Lru.dictionary.GetAlternateLookup() on each TryGet/TryRemove/GetOrAdd invocation. |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/49e070c4-dba5-4443-8b0a-829987bd0f73
Updated in b303547 — the API and tests now use |
|
|
||
| public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value) | ||
| { | ||
| var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>(); |
There was a problem hiding this comment.
@copilot can you make a new iteration where the AlternateLookup returned on this line is cached as a member variable?
There was a problem hiding this comment.
Done in 75d5105 — the AlternateLookup<TAlternateKey> adapter now caches the underlying ConcurrentDictionary<K, I>.AlternateLookup<TAlternateKey> as a member and reuses it for TryGet/TryRemove.
|
@copilot implement this feedback: the alternate lookup on the underlying ConcurrentDictionary<K, V> should be retrieved and cached in a private field of the AlternateLookup struct, as opposed to calling this.Lru.dictionary.GetAlternateLookup() on each TryGet/TryRemove/GetOrAdd invocation. |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/3133b4fb-b5d9-404b-8aeb-21f355942d69
This is already covered in 75d5105 — |
|
@copilot make a new internal static class called |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/99ca357c-842e-424b-a535-ad759177ad7c
... Implemented in b9ea646 — the alternate-key compatibility helpers now live in a new top-level internal Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
| } | ||
|
|
||
| K actualKey = this.Lru.dictionary.GetAlternateComparer<TAlternateKey, K, I>().Create(key); | ||
| if (this.Lru.dictionary.TryGetValue(actualKey, out var item) && this.Lru.GetOrDiscard(item, out value)) |
Co-authored-by: bitfaster <12851828+bitfaster@users.noreply.github.com> Agent-Logs-Url: https://github.com/bitfaster/BitFaster.Caching/sessions/098334d3-8129-4915-988d-958e2e0cbee3
| /// <param name="key">The alternate key.</param> | ||
| /// <param name="valueFactory">The value factory.</param> | ||
| /// <returns>The cached value.</returns> | ||
| TValue GetOrAdd(TAlternateKey key, Func<TAlternateKey, TValue> valueFactory); |
There was a problem hiding this comment.
GetOrAdd implies there is IAsyncAlternativeLookup with GetOrAddAsync.
It may be simpler to only have TryGet and TryRemove, thus the async variants are not needed. ConcurrentDictionary supports whole API surface including indexer, so precedent was set to be complete.
| /// <param name="factoryArgument">The factory argument.</param> | ||
| /// <returns>The cached value.</returns> | ||
| TValue GetOrAdd<TArg>(TAlternateKey key, Func<TAlternateKey, TArg, TValue> valueFactory, TArg factoryArgument); | ||
| } |
There was a problem hiding this comment.
- TryAdd
- TryUpdate
- AddOrUpdate
- indexer
This pulls the alternate-key cache work for
ConcurrentLruout of PR #638 into its own reviewable change. It adds a net9-only API for alternate lookups/removals/adds onConcurrentLruwhen the configured comparer implementsIAlternateEqualityComparer<TAlternateKey, TKey>.Alternate lookup API
GetAlternateLookup<TAlternateKey>()andTryGetAlternateLookup<TAlternateKey>(...)toConcurrentLruCore.IAlternateLookup<TAlternateKey, TKey, TValue>in theBitFaster.Cachingnamespace with:TryGetTryRemoveGetOrAddGetOrAdd<TArg>ConcurrentDictionary alternate lookup integration
ConcurrentDictionary<TKey, TValue>.GetAlternateLookup<TAlternateKey>()on net9 to perform alternate-key operations without first materializing the primary key on cache hits.IAlternateEqualityComparer<TAlternateKey, TKey>.Create(...)before falling back to the existing add path.Compatibility guardrails
Throw.IncompatibleComparer().NET9_0_OR_GREATERso the public surface remains unchanged on earlier targets.Targeted coverage
GetOrAddbehavior with alternate keysReadOnlySpan<char>as the positive alternate key in tests withStringComparer.Ordinal.Example:
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.