{"record":{"id":"84bd36c965780aeb","repo":"tui-cs/Terminal.Gui","slug":"error-cloning-dictionary-source-last-key-was","errorCode":null,"errorMessage":"Error cloning dictionary ({source}) (last key was \"{lastKey}\"). Ensure the source dictionary is not modified during cloning.","messagePattern":"Error cloning dictionary \\((.+?)\\) \\(last key was \"(.+?)\"\\)\\. Ensure the source dictionary is not modified during cloning\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"Terminal.Gui/Configuration/DeepCloner.cs","lineNumber":361,"sourceCode":"                    schemeDict [(string)clonedKey!] = (Scheme?)clonedValue;\n\n                    continue;\n                }\n\n                if (tempDict.Contains (clonedKey!))\n                {\n                    tempDict [clonedKey!] = clonedValue;\n                }\n                else\n                {\n                    tempDict.Add (clonedKey!, clonedValue);\n                }\n            }\n        }\n        catch (InvalidOperationException ex)\n        {\n            // Handle cases where the dictionary is modified during enumeration\n            throw new InvalidOperationException (\n                                                 $\"Error cloning dictionary ({source}) (last key was \\\"{lastKey}\\\"). Ensure the source dictionary is not modified during cloning.\",\n                                                 ex);\n        }\n\n        return tempDict;\n    }\n\n    [UnconditionalSuppressMessage (\"Trimming\", \"IL2067\", Justification = \"Dictionary cloning only instantiates supported dictionary runtime types and falls back safely when comparer constructors are unavailable.\")]\n    private static IDictionary CreateDictionaryInstance (Type dictType, object? comparer)\n    {\n        // Typed paths for dictionary types that require custom comparers.\n        if (dictType == typeof (ConcurrentDictionary<string, ThemeScope>))\n        {\n            if (comparer is IEqualityComparer<string> stringComparer)\n            {\n                return new ConcurrentDictionary<string, ThemeScope> (stringComparer);\n            }\n","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/tui-cs/Terminal.Gui/blob/2e47b11478db083499917f2ad27c34d30efb0df1/Terminal.Gui/Configuration/DeepCloner.cs#L343-L379","documentation":"Thrown by DeepCloner.CloneDictionary when an InvalidOperationException escapes the foreach over sourceDict.Keys. The catch at DeepCloner.cs:358-364 wraps it with the source's ToString and the last key being processed, because the canonical cause is concurrent modification of the source dictionary during enumeration. The inner exception carries the original failure (typically 'Collection was modified; enumeration operation may not execute').","triggerScenarios":"DeepClone runs on a dictionary while another thread (or a re-entrant clone callback) mutates the same dictionary instance. Concretely: the sourceDict.Keys enumerator detects a version change mid-iteration, or DeepCloneInternal on a value triggers a setter that adds/removes a key from the same source dictionary.","commonSituations":"ConfigurationManager.Apply runs on one thread while a theme change or settings reload mutates the same SettingsScope/ThemeScope dictionary on another. A Scheme or ConfigProperty setter, invoked during cloning, re-enters and modifies the dictionary being cloned. ConcurrentDictionary sources are less likely to throw here (snapshot enumeration), so this most often involves plain Dictionary<,>.","solutions":["Ensure the dictionary is not mutated during the clone: take a snapshot with ToArray()/new Dictionary(source) before calling DeepClone if concurrent mutation is possible.","Use ConcurrentDictionary<,> for any configuration dictionary that may be read during a clone on another thread (it enumerates over a snapshot and does not throw on concurrent modification).","Serialize access to the configuration scope with a lock around both mutation and Apply/clone operations.","Check whether a ConfigProperty setter or value-cloning callback re-enters ConfigurationManager and remove the re-entrancy.","Inspect the inner exception's stack trace to find the mutating call site."],"exampleFix":"// before — clone a dictionary that another thread may mutate\nvar clone = DeepCloner.DeepClone (sharedThemeDictionary);\n\n// after — snapshot first, then clone the snapshot\nvar snapshot = new ConcurrentDictionary<string, ThemeScope> (sharedThemeDictionary);\nvar clone = DeepCloner.DeepClone (snapshot);","handlingStrategy":"try-catch","validationCode":"// Snapshot a plain Dictionary before cloning if concurrent access is possible\nstatic Dictionary<TKey, TValue> SnapshotForClone<TKey, TValue> (Dictionary<TKey, TValue> source)\n    where TKey : notnull\n{\n    lock (source)\n    {\n        return new Dictionary<TKey, TValue> (source, source.Comparer);\n    }\n}\n\nvar safeCopy = SnapshotForClone (sharedDict);\nvar clone = DeepCloner.DeepClone (safeCopy);","typeGuard":"// Prefer ConcurrentDictionary for any dictionary cloned under concurrency\nstatic bool IsConcurrencySafeForClone (object? dict)\n    => dict is System.Collections.Concurrent.ConcurrentDictionary<,>;","tryCatchPattern":"try\n{\n    var clone = DeepCloner.DeepClone (configScope);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains (\"Ensure the source dictionary is not modified during cloning\"))\n{\n    // A concurrent modification happened. Snapshot the source and retry,\n    // or serialize Apply/clone with a lock against mutations.\n    var snapshot = sourceDict.ToArray ();\n    clone = DeepCloner.DeepClone (new Dictionary<,>(snapshot));\n}","preventionTips":["Use ConcurrentDictionary<,> for configuration dictionaries accessed across threads.","Lock around both mutation and Apply/clone when sharing a plain Dictionary.","Snapshot (ToArray/new Dictionary) before cloning if you cannot guarantee quiescence.","Avoid re-entrant ConfigurationManager calls from ConfigProperty setters."],"tags":["deepcloner","dictionaries","concurrency","thread-safety","configuration"],"backgroundTag":null,"analyzedSha":"2e47b11478db083499917f2ad27c34d30efb0df1","analyzedAt":"2026-08-13T19:20:08.826Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}