{"record":{"id":"67b66073de62d474","repo":"dotnet/wpf","slug":"sr-freezable-reentrant","errorCode":null,"errorMessage":"SR.Freezable_Reentrant","messagePattern":"SR\\.Freezable_Reentrant","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/FreezableCollection.cs","lineNumber":933,"sourceCode":"        ///         {\n        ///             CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index));\n        ///         }\n        /// </code>\n        /// </remarks>\n        private IDisposable BlockReentrancy()\n        {\n            _monitor.Enter();\n            return _monitor;\n        }\n\n        /// <summary> Check and assert for reentrant attempts to change this collection. </summary>\n        /// <exception cref=\"InvalidOperationException\"> raised when changing the collection\n        /// while another collection change is still being notified to other listeners </exception>\n        private void CheckReentrancy()\n        {\n            if (_monitor.Busy)\n            {\n                throw new InvalidOperationException(SR.Freezable_Reentrant);\n            }\n        }\n\n        #endregion ProtectedMethods\n\n        //------------------------------------------------------\n        //\n        //  Internal Fields\n        //\n        //------------------------------------------------------\n\n        #region Internal Fields\n\n        internal List<T> _collection;\n        internal uint _version = 0;\n\n        #endregion Internal Fields\n","sourceCodeStart":915,"sourceCodeEnd":951,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/FreezableCollection.cs#L915-L951","documentation":"CheckReentrancy throws InvalidOperationException(SR.Freezable_Reentrant) when the collection is modified (Clear, Insert, Remove, RemoveAt, Add) while a previous CollectionChanged notification is still being dispatched (_monitor.Busy). This prevents mutating the collection from inside a change handler, which would corrupt in-flight notifications for other listeners.","triggerScenarios":"Calling Add/Remove/Clear/etc. from inside a CollectionChanged event handler or a PropertyChanged handler triggered synchronously by the same collection change — e.g. handler does collection.Remove(item) while the Add notification is still propagating.","commonSituations":"Event handlers that 'fix up' the collection in response to a change, chained two-way data binding where updating one collection mutates another in the same notification pass, re-entrant UI logic in collection callbacks.","solutions":["Defer the mutation out of the handler: Dispatcher.BeginInvoke(() => collection.Add(...)) or queue the change and apply after the notification completes.","Restructure so handlers never mutate the same collection they are notified about.","Use a snapshot (ToList()) inside the handler and mutate the copy, applying the result later."],"exampleFix":"// before\nvoid OnCollectionChanged(...) { collection.Remove(badItem); } // reentrant\n// after\nvoid OnCollectionChanged(...) { Dispatcher.BeginInvoke(new Action(() => collection.Remove(badItem))); }","handlingStrategy":"try-catch","validationCode":"bool canMutate = !isInsideCollectionChangedHandler;\nif (!canMutate) queueMutation(() => collection.Add(item));","typeGuard":"null","tryCatchPattern":"try { collection.Add(item); }\ncatch (InvalidOperationException ex) when (isReentrancy(ex)) { Dispatcher.BeginInvoke(new Action(() => collection.Add(item))); }","preventionTips":["Never mutate the collection inside its own CollectionChanged handler","Defer mutations with Dispatcher.BeginInvoke or a pending-changes queue","Keep handlers read-only; apply writes after the notification completes"],"tags":["wpf","collection","reentrancy","events"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}