dotnet/wpf · error · InvalidOperationException

SR.LocalValueEnumerationInvalidated

Error message

SR.LocalValueEnumerationInvalidated

What it means

Thrown by SetEffectiveValue when the DependencyObject's effective-value table cannot be modified because an enumeration over LocalValueDictionary is in progress (CanModifyEffectiveValues == false). Modifying local values during that enumeration would corrupt the enumerator, so the property system blocks it.

Solutions

  1. Snapshot the enumerated entries (ToList()/ToArray()) first, then modify values after the loop ends
  2. Defer SetValue/ClearValue via Dispatcher.BeginInvoke if changes must happen during enumeration
  3. Restructure code so enumeration and mutation target different DependencyObjects
  4. Lock or single-thread the mutation path to avoid re-entrancy

Example fix

// before
foreach (var e in obj.ReadLocalValues()) { obj.ClearValue(e.Key); } // throws
// after
foreach (var e in obj.ReadLocalValues().ToList()) { obj.ClearValue(e.Key); }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!obj.CanModifyEffectiveValues) {
    pendingChanges.Add(() => obj.SetValue(dp, value)); // defer
} else { obj.SetValue(dp, value); }

Try / catch

try { obj.SetValue(dp, value); }
catch (InvalidOperationException) when (isEnumeratingLocalValues) { obj.Dispatcher.BeginInvoke(() => obj.SetValue(dp, value)); }

Prevention

When it happens

Trigger: Calling SetValue/ClearValue on a DependencyObject from inside a foreach over its ReadLocalValues / LocalValueDictionary on the same object; re-entrant property changes triggered from a local-value enumerator callback.

Common situations: Iterating local values to reset them and clearing inside the loop, diagnostics/logging code enumerating values while bindings update them, re-entrancy from value-change handlers fired during enumeration.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/5157bca64d0ced40. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/DependencyObject.cs:3114

            return new EntryIndex(iLo, found: false);
        }

        // insert the given entry at the given index
        // this function assumes that entryIndex is at the right
        // location such that the resulting list remains sorted by EffectiveValueEntry.PropertyIndex
        private void InsertEntry(EffectiveValueEntry entry, uint entryIndex)
        {
            // For thread-safety, sealed DOs can't modify _effectiveValues.
            Debug.Assert(!DO_Sealed, "A Sealed DO cannot be modified");

#if DEBUG
            EntryIndex debugIndex = LookupEntry(entry.PropertyIndex);
            Debug.Assert(!debugIndex.Found && debugIndex.Index == entryIndex, "Inserting duplicate");
#endif

            if (!CanModifyEffectiveValues)
            {
                throw new InvalidOperationException(SR.LocalValueEnumerationInvalidated);
            }

            uint effectiveValuesCount = EffectiveValuesCount;
            if (effectiveValuesCount > 0)
            {
                if (_effectiveValues.Length == effectiveValuesCount)
                {
                    int newSize = (int) (effectiveValuesCount * (IsInPropertyInitialization ? 2.0 : 1.2));
                    if (newSize == effectiveValuesCount)
                    {
                        newSize++;
                    }

                    EffectiveValueEntry[] destEntries = new EffectiveValueEntry[newSize];
                    Array.Copy(_effectiveValues, 0, destEntries, 0, entryIndex);
                    destEntries[entryIndex] = entry;
                    Array.Copy(_effectiveValues, entryIndex, destEntries, entryIndex + 1, effectiveValuesCount - entryIndex);
                    _effectiveValues = destEntries;

View on GitHub (pinned to 81131a70a4)