ConcurrentEvictingDictionary<TKey, TValue> Class
Definition
- Namespace
- Bodu.Collections.Generic.Concurrent
- Assembly
- Bodu.Collections.Concurrent.dll
- Package
- Bodu.Collections.Concurrent 1.0.0
Provides a thread-safe, fixed-capacity dictionary that automatically removes entries based on a chosen eviction policy, such as First-In-First-Out (FirstInFirstOut), Least Recently Used (LeastRecentlyUsed), or Least Frequently Used (LeastFrequentlyUsed).
public sealed class ConcurrentEvictingDictionary<TKey, TValue> : ICollection, IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable where TKey : notnull
Type Parameters
TKeySpecifies the type of keys in the dictionary (constraint:
where TKey : notnull).TValueSpecifies the type of values in the dictionary.
- Inheritance
-
ConcurrentEvictingDictionary<TKey, TValue>
- Implements
-
IDictionary<TKey, TValue>ICollection<KeyValuePair<TKey, TValue>>IReadOnlyDictionary<TKey, TValue>IReadOnlyCollection<KeyValuePair<TKey, TValue>>IEnumerable<KeyValuePair<TKey, TValue>>
- Inherited Members
- Extension Methods
Examples
var cache = new ConcurrentEvictingDictionary<string, byte[]>(
capacity: 1024, EvictingDictionaryPolicy.LeastRecentlyUsed);
Parallel.ForEach(requests, request =>
{
byte[] payload = cache.GetOrAdd(request.Key, key => LoadPayload(key));
Serve(request, payload);
});
Remarks
ConcurrentEvictingDictionary<TKey, TValue> is the thread-safe variant of EvictingDictionary<TKey, TValue>. It partitions its capacity across a fixed number of internal segments, each guarded by its own monitor: the key comparer's hash routes every key to exactly one segment, so operations on keys owned by different segments proceed in parallel. Reads are writes in an evicting cache - a lookup repositions the key for recency-tracked policies - so even TryGetValue(TKey, out TValue) takes its segment's lock; the striping keeps that contention local.
Eviction order is exact within a segment and approximate globally: each segment runs the configured EvictingDictionaryPolicy over its own slice of the capacity, so a heavily used segment may evict while other segments still have free slots. The sum of the segment slices equals Capacity exactly, so the dictionary never stores more than Capacity entries. Use the internal concurrency-level constructor (or a capacity of 1) to obtain a single segment whose eviction sequence matches the non-concurrent type exactly.
The single-key operations - Add(TKey, TValue), TryAdd(TKey, TValue), TryGetValue(TKey, out TValue), ContainsKey(TKey), Touch(TKey), TryRemove(TKey, out TValue), the indexer, and GetOrAdd(TKey, TValue) - are each individually atomic and lock only the owning segment. Count, IsEmpty, Clear(), ToArray(), and the snapshot properties acquire every segment lock and therefore observe a coherent point-in-time state; ApproximateCount is lock-free.
Enumeration iterates over a true snapshot captured when the enumerator is created (via ToArray()) and does not reflect subsequent changes. Unlike enumerators on non-concurrent collections, it never throws InvalidOperationException because of concurrent modification. The order of enumerated entries is unspecified.
Supplying an EvictingDictionaryExpiration at construction adds time-based expiry orthogonal to the capacity policy, with the same lazy-purge model as the non-concurrent type: expired entries are invisible to lookups and enumeration before they are physically removed, capacity pressure purges a segment's expired entries ahead of a policy eviction, and Count reports the raw stored count including expired-but-unpurged entries - call RemoveExpired() to reconcile. Without an expiration configuration the dictionary performs no clock reads.
Unlike EvictingDictionary<TKey, TValue>, this type exposes only the post-commit ItemEvicted event: under concurrency an eviction has already been committed by the time a handler could observe it, so a pre-removal event could not be honored. Handlers run after the segment lock has been released and their exceptions are suppressed (except OutOfMemoryException) - see the event's documentation.
Constructors
ConcurrentEvictingDictionary()
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the default capacity and eviction policy.
public ConcurrentEvictingDictionary()
Remarks
Creates an empty dictionary with a capacity of Bodu.Collections.Generic.Concurrent.ConcurrentEvictingDictionary`2.DefaultCapacity items, using Bodu.Collections.Generic.Concurrent.ConcurrentEvictingDictionary`2.DefaultPolicy for eviction when capacity is exceeded, and the default key comparer ( Default).
ConcurrentEvictingDictionary(IEnumerable<KeyValuePair<TKey, TValue>>)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the default capacity and eviction policy.
public ConcurrentEvictingDictionary(IEnumerable<KeyValuePair<TKey, TValue>> source)
Parameters
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
Remarks
If more elements are provided than the capacity allows, entries are evicted according to the policy.
Exceptions
- ArgumentNullException
sourceis null.
ConcurrentEvictingDictionary(IEnumerable<KeyValuePair<TKey, TValue>>, EvictingDictionaryPolicy)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the default capacity and the specified eviction policy.
public ConcurrentEvictingDictionary(IEnumerable<KeyValuePair<TKey, TValue>> source, EvictingDictionaryPolicy policy)
Parameters
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentOutOfRangeException
policyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity and the default eviction policy.
public ConcurrentEvictingDictionary(int capacity)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero.
ConcurrentEvictingDictionary(int, EvictingDictionaryExpiration?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity and time-based expiration, using the default eviction policy and key comparer.
public ConcurrentEvictingDictionary(int capacity, EvictingDictionaryExpiration? expiration)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
expirationEvictingDictionaryExpirationThe time-based expiration configuration, or null to disable expiry.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero.
ConcurrentEvictingDictionary(int, EvictingDictionaryPolicy)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity and eviction policy.
public ConcurrentEvictingDictionary(int capacity, EvictingDictionaryPolicy policy)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, EvictingDictionaryPolicy, EvictingDictionaryExpiration?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity, eviction policy, and time-based expiration, using the default key comparer.
public ConcurrentEvictingDictionary(int capacity, EvictingDictionaryPolicy policy, EvictingDictionaryExpiration? expiration)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
expirationEvictingDictionaryExpirationThe time-based expiration configuration, or null to disable expiry.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, EvictingDictionaryPolicy, IEqualityComparer<TKey>?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity, eviction policy, and key comparer.
public ConcurrentEvictingDictionary(int capacity, EvictingDictionaryPolicy policy, IEqualityComparer<TKey>? comparer)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
comparerIEqualityComparer<TKey>The equality comparer to use for keys, or null to use the default comparer.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, EvictingDictionaryPolicy, IEqualityComparer<TKey>?, EvictingDictionaryExpiration?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity, eviction policy, key comparer, and time-based expiration.
public ConcurrentEvictingDictionary(int capacity, EvictingDictionaryPolicy policy, IEqualityComparer<TKey>? comparer, EvictingDictionaryExpiration? expiration)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
comparerIEqualityComparer<TKey>The equality comparer to use for keys, or null to use the default comparer.
expirationEvictingDictionaryExpirationThe time-based expiration configuration, or null to disable expiry.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, IEnumerable<KeyValuePair<TKey, TValue>>)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the specified capacity and the default eviction policy.
public ConcurrentEvictingDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> source)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentOutOfRangeException
capacityis less than or equal to zero.
ConcurrentEvictingDictionary(int, IEnumerable<KeyValuePair<TKey, TValue>>, EvictingDictionaryPolicy)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the specified capacity and eviction policy.
public ConcurrentEvictingDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> source, EvictingDictionaryPolicy policy)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, IEnumerable<KeyValuePair<TKey, TValue>>, EvictingDictionaryPolicy, IEqualityComparer<TKey>?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the specified capacity, eviction policy, and key comparer.
public ConcurrentEvictingDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> source, EvictingDictionaryPolicy policy, IEqualityComparer<TKey>? comparer)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
comparerIEqualityComparer<TKey>The equality comparer to use for keys, or null to use the default comparer.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, IEnumerable<KeyValuePair<TKey, TValue>>, EvictingDictionaryPolicy, IEqualityComparer<TKey>?, EvictingDictionaryExpiration?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with elements copied from the specified sequence, using the specified capacity, eviction policy, key comparer, and time-based expiration.
public ConcurrentEvictingDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> source, EvictingDictionaryPolicy policy, IEqualityComparer<TKey>? comparer, EvictingDictionaryExpiration? expiration)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
sourceIEnumerable<KeyValuePair<TKey, TValue>>The sequence of key/value pairs to copy. Must not be null.
policyEvictingDictionaryPolicyThe eviction policy used when capacity is exceeded.
comparerIEqualityComparer<TKey>The equality comparer to use for keys, or null to use the default comparer.
expirationEvictingDictionaryExpirationThe time-based expiration configuration, or null to disable expiry.
Remarks
Copied entries receive the default TimeToLive (when configured); if more elements are provided than the capacity allows, entries are evicted according to the policy.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentOutOfRangeException
capacityis less than or equal to zero, orpolicyis not a defined EvictingDictionaryPolicy value.
ConcurrentEvictingDictionary(int, IEqualityComparer<TKey>?)
Initializes a new instance of the ConcurrentEvictingDictionary<TKey, TValue> class, with the specified capacity, using the default eviction policy and the specified key comparer.
public ConcurrentEvictingDictionary(int capacity, IEqualityComparer<TKey>? comparer)
Parameters
capacityintThe maximum number of key/value pairs the dictionary can contain. Must be positive.
comparerIEqualityComparer<TKey>The equality comparer to use for keys, or null to use the default comparer.
Exceptions
- ArgumentOutOfRangeException
capacityis less than or equal to zero.
Properties
ApproximateCount
Gets an approximate entry count without acquiring any segment lock.
public int ApproximateCount { get; }
Property Value
- int
The sum of the per-segment entry counters at the moment of the call. The result is correct when no other thread is concurrently mutating the dictionary; under concurrent writes the returned value may not reflect any single coherent point-in-time state. Like Count, it includes expired-but-unpurged entries.
Remarks
Use this property when callers need a fast size estimate - for capacity hints, telemetry, or display - but can tolerate values that lag active writers. Prefer Count when an exact snapshot is required.
Capacity
Gets the maximum number of items that can be stored in the dictionary before eviction occurs.
public int Capacity { get; }
Property Value
Comparer
Gets the equality comparer used for key identity, hash-table lookup, and segment routing.
public IEqualityComparer<TKey> Comparer { get; }
Property Value
- IEqualityComparer<TKey>
The active equality comparer.
Count
Gets the number of key/value pairs physically stored in the dictionary.
public int Count { get; }
Property Value
- int
The raw stored count, including expired entries that have not yet been purged.
Remarks
Reading this property acquires every segment lock, yielding an exact point-in-time count. Under concurrency the returned value may already be stale by the time the caller inspects it; prefer ApproximateCount for a lock-free estimate.
When time-based expiration is configured, this property deliberately reports the raw stored count - including expired-but-unpurged entries - matching EvictingDictionary<TKey, TValue>. Call RemoveExpired() to purge expired entries and reconcile Count with the live set.
EvictionCount
Gets the total number of items evicted from the dictionary since creation, whether by capacity pressure or time-based expiry.
public long EvictionCount { get; }
Property Value
Expiration
Gets the time-based expiration configuration for this dictionary, or null when expiration is disabled.
public EvictingDictionaryExpiration? Expiration { get; }
Property Value
- EvictingDictionaryExpiration
The EvictingDictionaryExpiration supplied at construction, or null when the dictionary was constructed without one. When null the dictionary performs no clock reads and behaves as a capacity-only cache.
IsEmpty
Gets a value indicating whether the dictionary is empty, observed as an exact point-in-time snapshot.
public bool IsEmpty { get; }
Property Value
Remarks
Reading this property acquires every segment lock, producing a coherent point-in-time view. Like Count, expired-but-unpurged entries count as stored.
IsReadOnly
Gets a value indicating whether the dictionary is read-only.
public bool IsReadOnly { get; }
Property Value
this[TKey]
Gets or sets the value associated with the specified key.
public TValue this[TKey key] { get; set; }
Parameters
keyTKeyThe key of the value to get or set.
Property Value
- TValue
The value associated with
key.
Remarks
The getter behaves as TryGetValue(TKey, out TValue) (a hit counts as a policy access, increments
TotalTouches, and slides a sliding deadline); the setter behaves as
Add(TKey, TValue) (add-or-replace). Each accessor is individually atomic, locking only the
segment that owns key.
Exceptions
- ArgumentNullException
keyis null.- KeyNotFoundException
The property is retrieved and no live entry exists for
key.
Keys
Gets a point-in-time snapshot of the dictionary's live keys.
public IReadOnlyCollection<TKey> Keys { get; }
Property Value
- IReadOnlyCollection<TKey>
A new read-only collection holding the keys present when the property was read.
Remarks
Policy
Gets the eviction policy configured for this dictionary.
public EvictingDictionaryPolicy Policy { get; }
Property Value
TotalTouches
Gets the total number of times any key has been successfully accessed or touched.
public long TotalTouches { get; }
Property Value
Values
Gets a point-in-time snapshot of the dictionary's live values.
public IReadOnlyCollection<TValue> Values { get; }
Property Value
- IReadOnlyCollection<TValue>
A new read-only collection holding the values present when the property was read.
Remarks
Methods
Add(TKey, TValue)
Adds the specified key and value to the dictionary, or replaces the value if the key already exists. If the owning segment has reached its capacity slice and the key is new, an existing entry is evicted according to the configured EvictingDictionaryPolicy.
public void Add(TKey key, TValue value)
Parameters
keyTKeyThe key of the element to add or update.
valueTValueThe value to associate with
key.
Remarks
When an entry for key already exists its value is updated in place and the write counts as a
touch against the eviction policy: recency-based policies move the entry to the most-recently-used position,
LeastFrequentlyUsed increments its accumulated frequency, and SecondChance marks it recently referenced. The
entry keeps its accumulated metadata rather than being treated as newly inserted. This differs from
Add(TKey, TValue), which throws on duplicate keys.
When time-based expiration is configured, each write is a fresh lease: the entry's lifetime restarts using the dictionary default TimeToLive (any per-entry override from a previous Add(TKey, TValue, TimeSpan) is discarded).
This operation is atomic and locks only the segment that owns key. Any eviction it causes
raises ItemEvicted after the segment lock has been released.
Exceptions
- ArgumentNullException
keyis null.
Add(TKey, TValue, TimeSpan)
Adds the specified key and value to the dictionary with an explicit time-to-live, or replaces the value and restarts the lifetime if the key already exists.
public void Add(TKey key, TValue value, TimeSpan timeToLive)
Parameters
keyTKeyThe key of the element to add or update.
valueTValueThe value to associate with
key.timeToLiveTimeSpanThe lifetime for this entry, overriding the dictionary's default TimeToLive.
Remarks
The per-entry timeToLive applies to this write only: a later update through
Add(TKey, TValue) or the indexer setter discards the override and re-applies the dictionary
default. Under Sliding, successful read accesses refresh the
deadline using this entry's own time-to-live. The operation is atomic and locks only the segment that owns
key.
Exceptions
- ArgumentNullException
keyis null.- ArgumentOutOfRangeException
timeToLiveis less than or equal to Zero.- InvalidOperationException
No Expiration configuration was supplied at construction.
AddOrUpdate(TKey, Func<TKey, TValue>, Func<TKey, TValue, TValue>)
Adds a key/value pair produced by addValueFactory if the key does not already exist, or
updates an existing entry by applying updateValueFactory to its current value, and returns
the resulting value.
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
Parameters
keyTKeyThe key of the element to add or update.
addValueFactoryFunc<TKey, TValue>The function that produces the value to add when
keyis absent.updateValueFactoryFunc<TKey, TValue, TValue>The function that produces the new value from the key and its existing value when
keyis present.
Returns
- TValue
The new value stored for
key: the value produced byaddValueFactoryon the add path, or the value produced byupdateValueFactoryon the update path.
Remarks
Both factories are invoked inside the owning segment's lock, so exactly one runs per call and it runs at most once - the single-flight behavior shared with GetOrAdd(TKey, Func<TKey, TValue>). A factory blocks every other operation on the same segment while it runs, so keep it short and never call back into this dictionary from it. If a factory throws, nothing is added or updated and the exception propagates.
The update path counts as an access for the configured EvictingDictionaryPolicy and starts a
fresh expiration lease; the add path evicts per the policy when the owning segment is full. The whole operation
is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
key,addValueFactory, orupdateValueFactoryis null.
AddOrUpdate(TKey, TValue, Func<TKey, TValue, TValue>)
Adds a key/value pair if the key does not already exist, or updates an existing entry by applying
updateValueFactory to its current value, and returns the resulting value.
public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
Parameters
keyTKeyThe key of the element to add or update.
addValueTValueThe value to add when
keyis absent.updateValueFactoryFunc<TKey, TValue, TValue>The function that produces the new value from the key and its existing value when
keyis present.
Returns
- TValue
The new value stored for
key:addValueon the add path, or the value produced byupdateValueFactoryon the update path.
Remarks
The update factory is invoked inside the owning segment's lock, so it runs at most once per call even under concurrent contention - the single-flight behavior shared with GetOrAdd(TKey, TValue). The factory blocks every other operation on the same segment while it runs, so keep it short and never call back into this dictionary from it. If the factory throws, nothing is added or updated and the exception propagates.
The update path counts as an access for the configured EvictingDictionaryPolicy and starts a
fresh expiration lease; the add path evicts per the policy when the owning segment is full. The whole operation
is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyorupdateValueFactoryis null.
Clear()
Removes all entries from the dictionary and resets the TotalTouches and EvictionCount counters.
public void Clear()
Remarks
This operation acquires every segment lock, so it is atomic with respect to all other operations. It is a bulk reset, not an eviction - ItemEvicted is not raised for the removed entries, matching Clear().
Contains(KeyValuePair<TKey, TValue>)
Determines whether the dictionary contains a live entry equal to the specified key/value pair.
public bool Contains(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The key/value pair to locate.
Returns
- bool
true if a live entry exists whose key matches
item.Keyand whose value equalsitem.Valueunder Default; otherwise, false.
Remarks
Like ContainsKey(TKey), this is a pure read with respect to the capacity policy: it does not reposition the key, update frequency metadata, or increment TotalTouches. An expired-but-unpurged entry counts as absent and is lazily removed as an eviction. The operation locks only the segment that owns the key.
Exceptions
- ArgumentNullException
item.Keyis null.
ContainsKey(TKey)
Determines whether the dictionary contains a live entry for the specified key.
public bool ContainsKey(TKey key)
Parameters
keyTKeyThe key to locate.
Returns
Remarks
This is a pure read with respect to both the capacity policy and time-based expiration - it does not update
recency or frequency metadata, does not increment TotalTouches, and does not refresh a sliding
expiration deadline (symmetric with Touch(TKey); use TryGetValue(TKey, out TValue) or the indexer getter
to slide). When time-based expiration is configured, an expired entry counts as absent: it is lazily removed as
an eviction and false is returned. The operation locks only the segment that owns
key.
Exceptions
- ArgumentNullException
keyis null.
CopyTo(KeyValuePair<TKey, TValue>[], int)
Copies the dictionary's live entries into a point-in-time snapshot written to the specified array, starting at the specified index.
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
Parameters
arrayKeyValuePair<TKey, TValue>[]The destination array. Must not be null.
arrayIndexintThe zero-based index in
arrayat which copying begins.
Remarks
This method takes an atomic snapshot of the dictionary (via ToArray()) before copying, so the destination reflects the state at the moment the snapshot was taken and is unaffected by concurrent modifications made afterward.
Exceptions
- ArgumentNullException
arrayis null.- ArgumentOutOfRangeException
arrayIndexis less than zero.- ArgumentException
The number of entries in the snapshot exceeds the available space from
arrayIndexto the end ofarray.
GetEnumerator()
Returns an enumerator that iterates over a point-in-time snapshot of the dictionary's live entries.
public ConcurrentEvictingDictionary<TKey, TValue>.Enumerator GetEnumerator()
Returns
- ConcurrentEvictingDictionary<TKey, TValue>.Enumerator
An ConcurrentEvictingDictionary<TKey, TValue>.Enumerator over the entries present when the enumerator was created.
Remarks
Enumeration operates on a snapshot captured via ToArray() at the moment this method is called. Entries added, removed, or evicted afterward are not reflected in the enumerated sequence.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections. The order of enumerated entries is unspecified.
GetOrAdd(TKey, Func<TKey, TValue>)
Adds a key/value pair to the dictionary by using the specified value factory if the key does not already exist, and returns either the existing or the newly created value.
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
Parameters
keyTKeyThe key of the element to get or add.
valueFactoryFunc<TKey, TValue>The function used to generate a value for the key when it is absent.
Returns
- TValue
The existing value when a live entry for
keyis present; otherwise the value produced byvalueFactory.
Remarks
Unlike GetOrAdd(TKey, Func<TKey, TValue>), the factory is invoked inside the owning segment's lock, so it runs at most once per key even under concurrent misses - the single-flight behavior that prevents cache stampedes.
The cost of that guarantee is that the factory blocks every other operation on the same segment while it runs: keep factories short, and never call back into this dictionary from a factory - doing so from the same thread re-enters the segment monitor and can corrupt eviction bookkeeping mid-add. If the factory throws, nothing is added and the exception propagates.
Exceptions
- ArgumentNullException
keyorvalueFactoryis null.
GetOrAdd(TKey, Func<TKey, TValue>, TimeSpan)
Adds a key/value pair produced by the specified value factory with an explicit time-to-live if the key does not already exist, and returns either the existing or the newly created value.
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory, TimeSpan timeToLive)
Parameters
keyTKeyThe key of the element to get or add.
valueFactoryFunc<TKey, TValue>The function used to generate a value for the key when it is absent.
timeToLiveTimeSpanThe lifetime applied when the entry is added, overriding the dictionary's default TimeToLive.
Returns
- TValue
The existing value when a live entry for
keyis present; otherwise the value produced byvalueFactory.
Remarks
The factory is invoked inside the owning segment's lock - at most once per key even under concurrent misses. See
GetOrAdd(TKey, Func<TKey, TValue>) for the factory caveats; the timeToLive is
applied only when the entry is added.
Exceptions
- ArgumentNullException
keyorvalueFactoryis null.- ArgumentOutOfRangeException
timeToLiveis less than or equal to Zero.- InvalidOperationException
No Expiration configuration was supplied at construction.
GetOrAdd(TKey, TValue)
Adds a key/value pair to the dictionary if the key does not already exist, and returns either the existing or the newly added value.
public TValue GetOrAdd(TKey key, TValue value)
Parameters
keyTKeyThe key of the element to get or add.
valueTValueThe value to add when the key is absent.
Returns
- TValue
The existing value when a live entry for
keyis present; otherwisevalue.
Remarks
A hit counts as an access for the configured policy (identical to TryGetValue(TKey, out TValue)) and increments
TotalTouches; a miss adds the entry, evicting per the policy when the owning segment is full. The
whole operation is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.
GetOrAdd(TKey, TValue, TimeSpan)
Adds a key/value pair with an explicit time-to-live if the key does not already exist, and returns either the existing or the newly added value.
public TValue GetOrAdd(TKey key, TValue value, TimeSpan timeToLive)
Parameters
keyTKeyThe key of the element to get or add.
valueTValueThe value to add when the key is absent.
timeToLiveTimeSpanThe lifetime applied when the entry is added, overriding the dictionary's default TimeToLive.
Returns
- TValue
The existing value when a live entry for
keyis present; otherwisevalue.
Remarks
A hit counts as an access for the configured policy and leaves the existing entry's lifetime rules unchanged
(sliding refresh applies as for TryGetValue(TKey, out TValue)); the timeToLive is applied only
when the entry is added. The whole operation is atomic and locks only the segment that owns
key.
Exceptions
- ArgumentNullException
keyis null.- ArgumentOutOfRangeException
timeToLiveis less than or equal to Zero.- InvalidOperationException
No Expiration configuration was supplied at construction.
Remove(KeyValuePair<TKey, TValue>)
Removes the entry equal to the specified key/value pair.
public bool Remove(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The key/value pair to remove.
Returns
- bool
true if a live entry whose key matches
item.Keyand whose value equalsitem.Valuewas found and removed; otherwise, false.
Remarks
The value comparison and removal happen atomically under the owning segment's lock, so a concurrent update of the value cannot cause a mismatched entry to be removed. An explicit removal is not an eviction - it does not raise ItemEvicted - though a lazily removed expired entry encountered along the way is surfaced as one.
Exceptions
- ArgumentNullException
item.Keyis null.
Remove(TKey)
Removes the entry with the specified key.
public bool Remove(TKey key)
Parameters
keyTKeyThe key of the entry to remove.
Returns
Remarks
This is the non-out companion to TryRemove(TKey, out TValue), sharing its semantics: it operates on physically
stored entries (removing an expired-but-unpurged entry and returning true), is not an
eviction (it does not raise ItemEvicted or increment EvictionCount), and locks
only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.
RemoveExpired()
Removes every expired entry from the dictionary and returns the number of entries removed.
public int RemoveExpired()
Returns
- int
The number of expired entries that were removed;
0when nothing had expired or when no Expiration configuration was supplied at construction.
Remarks
Each removal counts as an eviction: it raises ItemEvicted (after the segment locks are released) and increments EvictionCount, exactly as a capacity-triggered eviction does.
Segments are purged one at a time, each under its own lock, so the sweep is not atomic across the whole dictionary: an entry added to an already purged segment during the call is not examined, and entries may expire in later segments while earlier ones are being processed. Each segment's purge is individually atomic.
The dictionary never removes expired entries on a background timer; besides the lazy removal performed when an access touches an expired key, this method is the way to reconcile Count with the set of live entries. Call it periodically for caches that can sit idle for long periods.
ToArray()
Copies the dictionary's live entries into a new array.
public KeyValuePair<TKey, TValue>[] ToArray()
Returns
- KeyValuePair<TKey, TValue>[]
A new array containing a coherent point-in-time snapshot of the dictionary's live entries, or an empty array if the dictionary is empty. The order of entries is unspecified.
Remarks
This method acquires every segment lock for the duration of the copy, so the returned array reflects the dictionary's contents at a single instant and is unaffected by concurrent modifications made afterward.
When time-based expiration is configured, the clock is read once and expired entries are filtered out of the snapshot without being removed or having their deadlines refreshed, so the array length may be less than Count until RemoveExpired() reconciles the raw store.
Touch(TKey)
Marks the specified key as recently accessed without retrieving its value. If the eviction policy involves usage tracking, this updates the internal usage metadata.
public bool Touch(TKey key)
Parameters
keyTKeyThe key to touch.
Returns
Remarks
When time-based expiration is configured, an expired entry counts as absent: it is lazily removed as an eviction
and false is returned. Touch(TKey) affects only the capacity-policy metadata - it
does not refresh a sliding expiration deadline; use a value-returning read access ( TryGetValue(TKey, out TValue)
or the indexer getter) to slide ( ContainsKey(TKey), like Touch(TKey), is a pure read that
does not slide). The operation locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.
TryAdd(TKey, TValue)
Attempts to add the specified key and value to the dictionary, without replacing an existing live entry.
public bool TryAdd(TKey key, TValue value)
Parameters
keyTKeyThe key of the element to add.
valueTValueThe value to associate with
key.
Returns
Remarks
When time-based expiration is configured, an expired-but-unpurged entry for key does not
block the add: it is lazily removed as an eviction and the new entry is added, returning true.
This operation is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.
TryAdd(TKey, TValue, TimeSpan)
Attempts to add the specified key and value to the dictionary with an explicit time-to-live, without replacing an existing live entry.
public bool TryAdd(TKey key, TValue value, TimeSpan timeToLive)
Parameters
keyTKeyThe key of the element to add.
valueTValueThe value to associate with
key.timeToLiveTimeSpanThe lifetime for this entry, overriding the dictionary's default TimeToLive.
Returns
Remarks
An expired-but-unpurged entry for key does not block the add: it is lazily removed as an
eviction and the new entry is added, returning true. The operation is atomic and locks only
the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.- ArgumentOutOfRangeException
timeToLiveis less than or equal to Zero.- InvalidOperationException
No Expiration configuration was supplied at construction.
TryGetValue(TKey, out TValue)
Attempts to retrieve the value associated with the specified key.
public bool TryGetValue(TKey key, out TValue value)
Parameters
keyTKeyThe key of the value to retrieve.
valueTValueWhen this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter.
Returns
Remarks
A successful read counts as an access for the configured EvictingDictionaryPolicy: recency-tracked policies reposition the key, LeastFrequentlyUsed increments its frequency, and SecondChance sets its reference flag. TotalTouches is incremented on every successful lookup.
When time-based expiration is configured, an expired entry counts as absent (it is lazily removed as an eviction
and false is returned), and a hit refreshes the deadline under
Sliding. The operation locks only the segment that owns
key.
Exceptions
- ArgumentNullException
keyis null.
TryRemove(TKey, out TValue)
Attempts to remove the entry with the specified key, returning the removed value.
public bool TryRemove(TKey key, out TValue value)
Parameters
keyTKeyThe key of the entry to remove.
valueTValueWhen this method returns true, the value of the removed entry; otherwise, the default value for the type.
Returns
Remarks
TryRemove(TKey, out TValue) operates on physically stored entries: when time-based expiration is configured it also
removes an expired-but-unpurged entry and returns true. An explicit removal is not an
eviction - it does not raise ItemEvicted and does not increment EvictionCount. The
operation is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.
TryUpdate(TKey, TValue, TValue)
Atomically updates the value associated with key to newValue only when
its current value equals comparisonValue.
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
Parameters
keyTKeyThe key whose value is compared and possibly replaced.
newValueTValueThe value to store when the comparison succeeds.
comparisonValueTValueThe value the current entry must equal for the update to occur.
Returns
- bool
true if a live entry existed for
keyand its value equaledcomparisonValue(in which case it was replaced); otherwise, false.
Remarks
The comparison uses Default. A successful update counts as an access for the configured EvictingDictionaryPolicy - identical to the replace path of Add(TKey, TValue) - repositioning the key for recency-tracked policies and, when time-based expiration is configured, starting a fresh lease. A missing or expired key yields false; an expired-but-unpurged entry is lazily removed as an eviction before the method returns.
The whole operation is atomic and locks only the segment that owns key. Any eviction it
surfaces (an expired entry) raises ItemEvicted after the segment lock has been released.
Exceptions
- ArgumentNullException
keyis null.
Events
ItemEvicted
Occurs immediately after an entry has been evicted because of capacity pressure or time-based expiry.
public event Action<TKey, TValue>? ItemEvicted
Event Type
- Action<TKey, TValue>
Remarks
Handlers run on the thread whose operation caused the eviction, after the owning segment's lock has been released, so a handler can safely call back into the dictionary without deadlocking. The key and value provided are no longer present in the dictionary by the time the handler observes them. One caveat: because the segment monitor is reentrant, a GetOrAdd(TKey, Func<TKey, TValue>) factory that violates its documented no-re-entry rule and mutates the dictionary can cause the nested operation's handlers to run while the outer call still holds the stripe lock - a handler that then blocks on another thread needing that stripe deadlocks. Keeping factories free of dictionary calls (as their contract requires) preserves the outside-the-lock guarantee.
Each subscriber is invoked independently, and ordinary handler exceptions are caught and suppressed - only
OutOfMemoryException propagates. This differs from the non-concurrent
ItemEvicted, which propagates handler exceptions: under
concurrency the eviction has already been committed and observed by other threads, so propagating a handler
exception could not undo it. For the same reason there is no pre-removal ItemEvicting event on this type.
Explicit removals via TryRemove(TKey, out TValue) and bulk resets via Clear() are not evictions and do not raise this event.
Explicit Interface Implementations
ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue>)
Adds an item to the ICollection<T>.
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The object to add to the ICollection<T>.
Remarks
Follows the add-or-throw contract of
Add(TKey, TValue): a live entry for
item.Key causes an ArgumentException.
Exceptions
- NotSupportedException
The ICollection<T> is read-only.
IDictionary<TKey, TValue>.Add(TKey, TValue)
Adds the specified key and value to the dictionary, throwing if a live entry for the key already exists.
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
Parameters
keyTKeyThe key of the element to add.
valueTValueThe value to associate with
key.
Remarks
This is the IDictionary<TKey, TValue> add-or-throw contract, and it is
deliberately distinct from the public add-or-replace Add(TKey, TValue): it delegates to
TryAdd(TKey, TValue) and raises ArgumentException when the add is rejected. The
operation is atomic and locks only the segment that owns key.
Exceptions
- ArgumentNullException
keyis null.- ArgumentException
A live entry for
keyalready exists.
IDictionary<TKey, TValue>.Keys
Gets an ICollection<T> containing the keys of the IDictionary<TKey, TValue>.
ICollection<TKey> IDictionary<TKey, TValue>.Keys { get; }
Returns
- ICollection<TKey>
An ICollection<T> containing the keys of the object that implements IDictionary<TKey, TValue>.
Remarks
Returns a point-in-time snapshot of the live keys wrapped as a read-only ICollection<T>; mutating the returned collection is not supported. This is the same snapshot produced by the public Keys property.
IDictionary<TKey, TValue>.Values
Gets an ICollection<T> containing the values in the IDictionary<TKey, TValue>.
ICollection<TValue> IDictionary<TKey, TValue>.Values { get; }
Returns
- ICollection<TValue>
An ICollection<T> containing the values in the object that implements IDictionary<TKey, TValue>.
Remarks
Returns a point-in-time snapshot of the live values wrapped as a read-only ICollection<T>; mutating the returned collection is not supported. This is the same snapshot produced by the public Values property.
IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
Returns an enumerator that iterates over a point-in-time snapshot of the dictionary's live entries.
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
Returns
- IEnumerator<KeyValuePair<TKey, TValue>>
An ConcurrentEvictingDictionary<TKey, TValue>.Enumerator over the entries present when the enumerator was created.
Remarks
Enumeration operates on a snapshot captured via ToArray() at the moment this method is called. Entries added, removed, or evicted afterward are not reflected in the enumerated sequence.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections. The order of enumerated entries is unspecified.
IReadOnlyDictionary<TKey, TValue>.Keys
Gets an enumerable collection that contains the keys in the read-only dictionary.
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys { get; }
Returns
- IEnumerable<TKey>
An enumerable collection that contains the keys in the read-only dictionary.
IReadOnlyDictionary<TKey, TValue>.Values
Gets an enumerable collection that contains the values in the read-only dictionary.
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values { get; }
Returns
- IEnumerable<TValue>
An enumerable collection that contains the values in the read-only dictionary.
ICollection.CopyTo(Array, int)
Copies the live entries of the ConcurrentEvictingDictionary<TKey, TValue> to a one-dimensional, zero-based Array, starting at the specified index.
void ICollection.CopyTo(Array array, int index)
Parameters
arrayArrayThe destination array. Must not be null, must be single-dimensional, and must have zero-based indexing.
indexintThe zero-based index in
arrayat which copying begins.
Remarks
This method takes an atomic snapshot of the dictionary before copying. The destination array reflects the state of the dictionary at the moment the snapshot was taken and is not affected by concurrent modifications made afterward.
Exceptions
- ArgumentNullException
arrayis null.- ArgumentException
arrayis multidimensional, does not have zero-based indexing, its element type is incompatible with KeyValuePair<TKey, TValue>, or the number of entries in the snapshot exceeds the available space fromindexto the end ofarray.- ArgumentOutOfRangeException
indexis less than zero.
ICollection.IsSynchronized
Gets a value indicating whether access to the ConcurrentEvictingDictionary<TKey, TValue> is synchronized (thread safe).
bool ICollection.IsSynchronized { get; }
Returns
- bool
Always false. ConcurrentEvictingDictionary<TKey, TValue> manages its own internal synchronization and does not expose a public lock object.
Remarks
Thread safety is achieved through internal lock striping. Callers should not attempt to coordinate access externally via SyncRoot, as that property is not supported.
ICollection.SyncRoot
Gets an object that can be used to synchronize access to the collection. Not supported on this type - ConcurrentEvictingDictionary<TKey, TValue> manages its own internal synchronization.
object ICollection.SyncRoot { get; }
Returns
Remarks
Exposing a SyncRoot would allow callers to take the same locks used internally, undermining the concurrency guarantees of the collection. This matches the behavior of ConcurrentDictionary<TKey, TValue> and other BCL concurrent collections.
Exceptions
- NotSupportedException
Always thrown. Use the thread-safe members of this class directly.
IEnumerable.GetEnumerator()
Returns an enumerator that iterates over a point-in-time snapshot of the dictionary's live entries.
IEnumerator IEnumerable.GetEnumerator()
Returns
- IEnumerator
An ConcurrentEvictingDictionary<TKey, TValue>.Enumerator over the entries present when the enumerator was created.
Remarks
Enumeration operates on a snapshot captured via ToArray() at the moment this method is called. Entries added, removed, or evicted afterward are not reflected in the enumerated sequence.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections. The order of enumerated entries is unspecified.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |