Table of Contents

ConcurrentEvictingDictionary<TKey, TValue> Class

Definition

Namespace
Bodu.Collections.Generic.Concurrent
Assembly
Bodu.Collections.Concurrent.dll
Package
Bodu.Collections.Concurrent 1.0.0
Source
ConcurrentEvictingDictionary{T,T}.Atomic.cs

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

TKey

Specifies the type of keys in the dictionary (constraint: where TKey : notnull).

TValue

Specifies the type of values in the dictionary.

Inheritance
ConcurrentEvictingDictionary<TKey, TValue>
Implements
IDictionary<TKey, TValue>
ICollection<KeyValuePair<TKey, TValue>>
IReadOnlyDictionary<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

source IEnumerable<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

source is 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

source IEnumerable<KeyValuePair<TKey, TValue>>

The sequence of key/value pairs to copy. Must not be null.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

Exceptions

ArgumentNullException

source is null.

ArgumentOutOfRangeException

policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

Exceptions

ArgumentOutOfRangeException

capacity is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

expiration EvictingDictionaryExpiration

The time-based expiration configuration, or null to disable expiry.

Exceptions

ArgumentOutOfRangeException

capacity is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

Exceptions

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

expiration EvictingDictionaryExpiration

The time-based expiration configuration, or null to disable expiry.

Exceptions

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

comparer IEqualityComparer<TKey>

The equality comparer to use for keys, or null to use the default comparer.

Exceptions

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

comparer IEqualityComparer<TKey>

The equality comparer to use for keys, or null to use the default comparer.

expiration EvictingDictionaryExpiration

The time-based expiration configuration, or null to disable expiry.

Exceptions

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

source IEnumerable<KeyValuePair<TKey, TValue>>

The sequence of key/value pairs to copy. Must not be null.

Exceptions

ArgumentNullException

source is null.

ArgumentOutOfRangeException

capacity is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

source IEnumerable<KeyValuePair<TKey, TValue>>

The sequence of key/value pairs to copy. Must not be null.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

Exceptions

ArgumentNullException

source is null.

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

source IEnumerable<KeyValuePair<TKey, TValue>>

The sequence of key/value pairs to copy. Must not be null.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

comparer IEqualityComparer<TKey>

The equality comparer to use for keys, or null to use the default comparer.

Exceptions

ArgumentNullException

source is null.

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

source IEnumerable<KeyValuePair<TKey, TValue>>

The sequence of key/value pairs to copy. Must not be null.

policy EvictingDictionaryPolicy

The eviction policy used when capacity is exceeded.

comparer IEqualityComparer<TKey>

The equality comparer to use for keys, or null to use the default comparer.

expiration EvictingDictionaryExpiration

The 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

source is null.

ArgumentOutOfRangeException

capacity is less than or equal to zero, or policy is 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

capacity int

The maximum number of key/value pairs the dictionary can contain. Must be positive.

comparer IEqualityComparer<TKey>

The equality comparer to use for keys, or null to use the default comparer.

Exceptions

ArgumentOutOfRangeException

capacity is 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

int

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

long

The cumulative eviction count. Reset to zero by Clear().

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

bool

true if the dictionary stores no entries; otherwise, false.

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

bool

Always false; the dictionary supports additions, updates, and removals.

this[TKey]

Gets or sets the value associated with the specified key.

public TValue this[TKey key] { get; set; }

Parameters

key TKey

The 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

key is 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

Unlike Keys, this is a snapshot rather than a live view: reading the property acquires every segment lock and copies the keys (mirroring Keys), so each read allocates and later mutations are not reflected. Expired-but-unpurged entries are excluded.

Policy

Gets the eviction policy configured for this dictionary.

public EvictingDictionaryPolicy Policy { get; }

Property Value

EvictingDictionaryPolicy

TotalTouches

Gets the total number of times any key has been successfully accessed or touched.

public long TotalTouches { get; }

Property Value

long

The cumulative touch count. Reset to zero by Clear().

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

Unlike Values, this is a snapshot rather than a live view: reading the property acquires every segment lock and copies the values (mirroring Values), so each read allocates and later mutations are not reflected. Expired-but-unpurged entries are excluded.

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

key TKey

The key of the element to add or update.

value TValue

The 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

key is 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

key TKey

The key of the element to add or update.

value TValue

The value to associate with key.

timeToLive TimeSpan

The 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

key is null.

ArgumentOutOfRangeException

timeToLive is 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

key TKey

The key of the element to add or update.

addValueFactory Func<TKey, TValue>

The function that produces the value to add when key is absent.

updateValueFactory Func<TKey, TValue, TValue>

The function that produces the new value from the key and its existing value when key is present.

Returns

TValue

The new value stored for key: the value produced by addValueFactory on the add path, or the value produced by updateValueFactory on 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, or updateValueFactory is 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

key TKey

The key of the element to add or update.

addValue TValue

The value to add when key is absent.

updateValueFactory Func<TKey, TValue, TValue>

The function that produces the new value from the key and its existing value when key is present.

Returns

TValue

The new value stored for key: addValue on the add path, or the value produced by updateValueFactory on 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

key or updateValueFactory is 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

item KeyValuePair<TKey, TValue>

The key/value pair to locate.

Returns

bool

true if a live entry exists whose key matches item.Key and whose value equals item.Value under 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.Key is null.

ContainsKey(TKey)

Determines whether the dictionary contains a live entry for the specified key.

public bool ContainsKey(TKey key)

Parameters

key TKey

The key to locate.

Returns

bool

true if a live entry exists for key; otherwise, false.

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

key is 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

array KeyValuePair<TKey, TValue>[]

The destination array. Must not be null.

arrayIndex int

The zero-based index in array at 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

array is null.

ArgumentOutOfRangeException

arrayIndex is less than zero.

ArgumentException

The number of entries in the snapshot exceeds the available space from arrayIndex to the end of array.

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

key TKey

The key of the element to get or add.

valueFactory Func<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 key is present; otherwise the value produced by valueFactory.

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

key or valueFactory is 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

key TKey

The key of the element to get or add.

valueFactory Func<TKey, TValue>

The function used to generate a value for the key when it is absent.

timeToLive TimeSpan

The lifetime applied when the entry is added, overriding the dictionary's default TimeToLive.

Returns

TValue

The existing value when a live entry for key is present; otherwise the value produced by valueFactory.

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

key or valueFactory is null.

ArgumentOutOfRangeException

timeToLive is 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

key TKey

The key of the element to get or add.

value TValue

The value to add when the key is absent.

Returns

TValue

The existing value when a live entry for key is present; otherwise value.

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

key is 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

key TKey

The key of the element to get or add.

value TValue

The value to add when the key is absent.

timeToLive TimeSpan

The lifetime applied when the entry is added, overriding the dictionary's default TimeToLive.

Returns

TValue

The existing value when a live entry for key is present; otherwise value.

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

key is null.

ArgumentOutOfRangeException

timeToLive is 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

item KeyValuePair<TKey, TValue>

The key/value pair to remove.

Returns

bool

true if a live entry whose key matches item.Key and whose value equals item.Value was 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.Key is null.

Remove(TKey)

Removes the entry with the specified key.

public bool Remove(TKey key)

Parameters

key TKey

The key of the entry to remove.

Returns

bool

true if an entry was found and removed; otherwise, false.

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

key is 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; 0 when 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

key TKey

The key to touch.

Returns

bool

true if a live entry exists and was marked as accessed; otherwise, false.

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

key is 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

key TKey

The key of the element to add.

value TValue

The value to associate with key.

Returns

bool

true if the entry was added; false if a live entry for key already exists.

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

key is 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

key TKey

The key of the element to add.

value TValue

The value to associate with key.

timeToLive TimeSpan

The lifetime for this entry, overriding the dictionary's default TimeToLive.

Returns

bool

true if the entry was added; false if a live (non-expired) entry for key already exists.

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

key is null.

ArgumentOutOfRangeException

timeToLive is 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

key TKey

The key of the value to retrieve.

value TValue

When 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

bool

true if the dictionary contains a live entry for the specified key; otherwise, false.

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

key is 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

key TKey

The key of the entry to remove.

value TValue

When this method returns true, the value of the removed entry; otherwise, the default value for the type.

Returns

bool

true if an entry was found and removed; otherwise, false.

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

key is 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

key TKey

The key whose value is compared and possibly replaced.

newValue TValue

The value to store when the comparison succeeds.

comparisonValue TValue

The value the current entry must equal for the update to occur.

Returns

bool

true if a live entry existed for key and its value equaled comparisonValue (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

key is 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

item KeyValuePair<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

key TKey

The key of the element to add.

value TValue

The 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

key is null.

ArgumentException

A live entry for key already 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

array Array

The destination array. Must not be null, must be single-dimensional, and must have zero-based indexing.

index int

The zero-based index in array at 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

array is null.

ArgumentException

array is 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 from index to the end of array.

ArgumentOutOfRangeException

index is 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

object

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

ProductVersions
.NET8, 10