LayeredDictionary<TKey, TValue> Class
Definition
Represents a live, read-through view over an ordered list of underlying dictionaries in which the first layer containing a key supplies its value, and all writes are applied to the first layer only.
public sealed class LayeredDictionary<TKey, TValue> : 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.
TValueSpecifies the type of values in the dictionary.
- Inheritance
-
LayeredDictionary<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
Remarks
LayeredDictionary<TKey, TValue> is the .NET analogue of Python's collections.ChainMap: it
groups several dictionaries into a single updatable view without copying them. Lookups search the layers in order
and the first layer containing the key wins - an entry in an earlier layer shadows any entry with the
same key in later layers. The same first-wins precedence model governs Bodu.Text.Configuration's resolver
chain, where earlier configuration sources take precedence over later ones.
The layer list is fixed at construction, but each layer is held by reference and remains fully live:
mutations made directly to an underlying dictionary are immediately visible through the view. All mutations made through
the view - Add(TKey, TValue), the indexer setter, Remove(TKey), and
Clear() - affect the first layer only, exactly matching Python ChainMap semantics. In
particular, Remove(TKey) returns false for a key that exists only in deeper
layers, and removing a first-layer entry that shadowed a deeper one makes the deeper value visible again (the
"unshadowing" behaviour).
Count, Keys, Values, and enumeration present the merged view: distinct keys across all layers with first-wins values. These operations walk every layer and track seen keys, so they cost O(n) in the total number of entries across all layers - Count is not a cached O(1) property. Keys and Values return snapshots materialized at the time of the call; enumeration is lazy and reflects the layers as it walks them.
The optional key comparer supplied at construction is used only by the view's own distinct-key logic (the seen-key set behind Count and enumeration). Each underlying dictionary continues to use its own comparer for its lookups, so mismatched comparers can produce surprising shadowing - for example, a case-insensitive first layer can shadow keys the view's case-sensitive comparer considers distinct. Prefer constructing the view and all layers with the same comparer.
The non-generic IDictionary and ICollection surfaces are deliberately not implemented: this type is a lightweight view over existing dictionaries rather than a stand-alone collection, and the legacy interfaces add no value to it.
LayeredDictionary<TKey, TValue> is not thread-safe. Concurrent reads and writes, including direct mutations of the underlying layers, require external synchronization.
var overrides = new Dictionary<string, string>();
var defaults = new Dictionary<string, string> { ["colour"] = "blue", ["size"] = "medium" };
var settings = new LayeredDictionary<string, string>(overrides, defaults);
string colour = settings["colour"]; // "blue" - falls through to the defaults layer
settings["colour"] = "red"; // writes to the overrides layer, shadowing the default
colour = settings["colour"]; // "red" - the first layer wins
settings.Remove("colour"); // removes from the overrides layer only…
colour = settings["colour"]; // "blue" - the default is unshadowed
Constructors
LayeredDictionary(params IDictionary<TKey, TValue>[])
Initializes a new instance of the LayeredDictionary<TKey, TValue> class over the specified layers, using the default key comparer for the view's distinct-key logic.
public LayeredDictionary(params IDictionary<TKey, TValue>[] layers)
Parameters
layersIDictionary<TKey, TValue>[]The underlying dictionaries in precedence order; the first layer wins on read and receives all writes. Must not be null, must not be empty, and must not contain null elements.
Exceptions
- ArgumentNullException
layersis null.- ArgumentException
layersis empty, or contains a null element.
LayeredDictionary(IEnumerable<IDictionary<TKey, TValue>>)
Initializes a new instance of the LayeredDictionary<TKey, TValue> class over the specified layer sequence, using the default key comparer for the view's distinct-key logic.
public LayeredDictionary(IEnumerable<IDictionary<TKey, TValue>> layers)
Parameters
layersIEnumerable<IDictionary<TKey, TValue>>The underlying dictionaries in precedence order; the first layer wins on read and receives all writes. Must not be null, must not be empty, and must not contain null elements.
Exceptions
- ArgumentNullException
layersis null.- ArgumentException
layersis empty, or contains a null element.
LayeredDictionary(IEnumerable<IDictionary<TKey, TValue>>, IEqualityComparer<TKey>?)
Initializes a new instance of the LayeredDictionary<TKey, TValue> class over the specified layer sequence, using the specified key comparer for the view's distinct-key logic.
public LayeredDictionary(IEnumerable<IDictionary<TKey, TValue>> layers, IEqualityComparer<TKey>? comparer)
Parameters
layersIEnumerable<IDictionary<TKey, TValue>>The underlying dictionaries in precedence order; the first layer wins on read and receives all writes. Must not be null, must not be empty, and must not contain null elements.
comparerIEqualityComparer<TKey>The equality comparer used by the view's distinct-key logic, or null to use the default comparer.
Remarks
The sequence is materialized defensively: later changes to layers itself do not affect the
view, but the referenced dictionaries remain live. The comparer governs only how the view deduplicates
keys across layers; each underlying dictionary keeps using its own comparer for lookups.
Exceptions
- ArgumentNullException
layersis null.- ArgumentException
layersis empty, or contains a null element.
Properties
Comparer
Gets the IEqualityComparer<T> used by the view's distinct-key logic.
public IEqualityComparer<TKey> Comparer { get; }
Property Value
- IEqualityComparer<TKey>
The comparer used to deduplicate keys across layers during Count and enumeration.
Remarks
This comparer does not influence lookups - each underlying layer resolves keys with its own comparer. When the layers and the view disagree on key equality, shadowing may not match the view's notion of "same key".
Count
Gets the number of elements contained in the ICollection<T>.
public int Count { get; }
Property Value
- int
The number of elements contained in the ICollection<T>.
Remarks
Counts the distinct keys across all layers under Comparer. This walks every layer and costs O(n) in the total number of entries across all layers on every call; it is not a cached value.
IsReadOnly
Gets a value indicating whether the ICollection<T> is read-only.
public bool IsReadOnly { get; }
Property Value
- bool
true if the ICollection<T> is read-only; otherwise, false.
this[TKey]
Gets or sets the element with the specified key.
public TValue this[TKey key] { get; set; }
Parameters
keyTKeyThe key of the element to get or set.
Property Value
- TValue
The element with the specified key.
Remarks
The getter searches the layers in order and returns the value from the first layer containing the key; deeper entries with the same key are shadowed.
The setter writes to the first layer only. Assigning a key that resolves from a deeper layer creates or updates a first-layer entry that shadows the deeper one; the deeper entry itself is never modified.
Exceptions
- ArgumentNullException
keyis null.- KeyNotFoundException
The property is retrieved and
keyis not found.- NotSupportedException
The property is set and the IDictionary<TKey, TValue> is read-only.
Keys
Gets an ICollection<T> containing the keys of the IDictionary<TKey, TValue>.
public ICollection<TKey> Keys { get; }
Property Value
- ICollection<TKey>
An ICollection<T> containing the keys of the object that implements IDictionary<TKey, TValue>.
Remarks
Returns a read-only snapshot of the distinct keys across all layers, in first-wins enumeration order, materialized at the time of the call. The returned collection is not write-through - its mutating members throw NotSupportedException - and later mutations of the layers are not reflected in a previously returned collection.
Layers
Gets the ordered list of underlying layers this view reads through.
public IReadOnlyList<IDictionary<TKey, TValue>> Layers { get; }
Property Value
- IReadOnlyList<IDictionary<TKey, TValue>>
A read-only list of the layer dictionaries in precedence order; index 0 is the write layer and wins on read.
Remarks
The list itself is fixed at construction, but each element is the live underlying dictionary - mutating a layer directly is immediately visible through the view.
Values
Gets an ICollection<T> containing the values in the IDictionary<TKey, TValue>.
public ICollection<TValue> Values { get; }
Property Value
- ICollection<TValue>
An ICollection<T> containing the values in the object that implements IDictionary<TKey, TValue>.
Remarks
Returns a read-only snapshot of the first-wins values for the distinct keys across all layers, materialized at the time of the call. The returned collection is not write-through - its mutating members throw NotSupportedException - and later mutations of the layers are not reflected in a previously returned collection.
Methods
Add(KeyValuePair<TKey, TValue>)
Adds the specified key/value pair to the first layer.
public void Add(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The key/value pair to add to the first layer.
Exceptions
- ArgumentNullException
item.Keyis null.- ArgumentException
The first layer already contains an element with the same key.
Add(TKey, TValue)
Adds the specified key and value to the first layer.
public void Add(TKey key, TValue value)
Parameters
keyTKeyThe key of the element to add. Must not be null.
valueTValueThe value to associate with
key.
Remarks
Only the first layer is consulted for the duplicate check: adding a key that exists solely in deeper layers succeeds and the new first-layer entry shadows the deeper value. The operation throws only when the first layer itself already contains the key.
Exceptions
- ArgumentNullException
keyis null.- ArgumentException
The first layer already contains an element with the same key.
Clear()
Removes all entries from the first layer.
public void Clear()
Remarks
Matching Python ChainMap semantics, only the first layer is cleared; deeper layers are untouched, so any
keys they contain remain visible through the view - including keys the cleared entries previously shadowed.
Contains(KeyValuePair<TKey, TValue>)
Determines whether the ICollection<T> contains a specific value.
public bool Contains(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The object to locate in the ICollection<T>.
Returns
- bool
true if
itemis found in the ICollection<T>; otherwise, false.
Remarks
The pair matches when the view resolves item.Key (first-wins) to a value equal to item.Value under
Default. A pair whose value exists only in a
shadowed deeper layer does not match.
ContainsKey(TKey)
Determines whether the IDictionary<TKey, TValue> contains an element with the specified key.
public bool ContainsKey(TKey key)
Parameters
keyTKeyThe key to locate in the IDictionary<TKey, TValue>.
Returns
- bool
true if the IDictionary<TKey, TValue> contains an element with the key; otherwise, false.
Remarks
Returns true when any layer contains the key, each layer deciding membership with its own comparer.
Exceptions
- ArgumentNullException
keyis null.
CopyTo(KeyValuePair<TKey, TValue>[], int)
Copies the elements of the ICollection<T> to an Array, starting at a particular Array index.
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
Parameters
arrayKeyValuePair<TKey, TValue>[]The one-dimensional Array that is the destination of the elements copied from ICollection<T>. The Array must have zero-based indexing.
arrayIndexintThe zero-based index in
arrayat which copying begins.
Remarks
Copies the merged view - distinct keys with first-wins values - in enumeration order.
Exceptions
- ArgumentNullException
arrayis null.- ArgumentOutOfRangeException
arrayIndexis less than 0.- ArgumentException
The number of elements in the source ICollection<T> is greater than the available space from
arrayIndexto the end of the destinationarray.
GetEnumerator()
Returns an enumerator that iterates through the collection.
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
Returns
- IEnumerator<KeyValuePair<TKey, TValue>>
An enumerator that can be used to iterate through the collection.
Remarks
Enumerates the distinct keys across all layers with first-wins values: each layer is walked in order and a key is yielded the first time it is seen (under Comparer); later occurrences are skipped.
Enumeration is lazy and reads the live layers as it walks them; mutating a layer during enumeration follows that layer's own enumerator-invalidation rules.
Remove(KeyValuePair<TKey, TValue>)
Removes the first occurrence of a specific object from the ICollection<T>.
public bool Remove(KeyValuePair<TKey, TValue> item)
Parameters
itemKeyValuePair<TKey, TValue>The object to remove from the ICollection<T>.
Returns
- bool
true if
itemwas successfully removed from the ICollection<T>; otherwise, false. This method also returns false ifitemis not found in the original ICollection<T>.
Remarks
The pair is removed only when the view resolves it (per Contains(KeyValuePair<TKey, TValue>)) and the first layer contains the key; deeper layers are never mutated.
Exceptions
- NotSupportedException
The ICollection<T> is read-only.
Remove(TKey)
Removes the entry with the specified key from the first layer.
public bool Remove(TKey key)
Parameters
keyTKeyThe key of the entry to remove. Must not be null.
Returns
- bool
true if the entry was removed from the first layer; false if the first layer did not contain the key - including when the key exists only in deeper layers.
Remarks
Matching Python ChainMap semantics, only the first layer is mutated. Removing a first-layer entry that
shadowed a deeper entry makes the deeper value visible again ("unshadowing"): the key remains present in the
view with the next layer's value. A key that exists only in deeper layers is not removed and the method returns
false.
Exceptions
- ArgumentNullException
keyis null.
TryGetValue(TKey, out TValue)
Attempts to retrieve the value associated with the specified key, searching the layers in order.
public bool TryGetValue(TKey key, out TValue value)
Parameters
keyTKeyThe key of the value to retrieve. Must not be null.
valueTValueWhen this method returns, contains the value from the first layer containing the key, if any layer contains it; otherwise, the default value for the type of the value parameter.
Returns
Exceptions
- ArgumentNullException
keyis null.
Explicit Interface Implementations
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.
IEnumerable.GetEnumerator()
Returns an enumerator that iterates through a collection.
IEnumerator IEnumerable.GetEnumerator()
Returns
- IEnumerator
An IEnumerator object that can be used to iterate through the collection.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |