diff --git a/src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs b/src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs index 41fa00a6bb7c..e0f82daf4b57 100644 --- a/src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs +++ b/src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs @@ -172,7 +172,13 @@ public void SetPlatformRenderMode(IComponentRenderMode renderMode) private void InferRenderModes(Renderer renderer) { - for (var i = 0; i < _registeredCallbacks.Count; i++) + // We are iterating backwards to allow the callbacks to remove themselves from the list. + // Otherwise, we would have to make a copy of the list to avoid running into situations + // where we don't run all the callbacks because the count of the list changed while we + // were iterating over it. + // It is not allowed to register a callback while we are persisting the state, so we don't + // need to worry about new callbacks being added to the list. + for (var i = _registeredCallbacks.Count - 1; i >= 0; i--) { var registration = _registeredCallbacks[i]; if (registration.RenderMode != null) diff --git a/src/Components/Components/test/PersistentState/ComponentStatePersistenceManagerTest.cs b/src/Components/Components/test/PersistentState/ComponentStatePersistenceManagerTest.cs index a8857107a782..34046eaf2342 100644 --- a/src/Components/Components/test/PersistentState/ComponentStatePersistenceManagerTest.cs +++ b/src/Components/Components/test/PersistentState/ComponentStatePersistenceManagerTest.cs @@ -353,6 +353,53 @@ public async Task PersistStateAsync_InvokesAllCallbacksEvenIfACallbackIsRemovedA Assert.Equal(4, executionSequence.Count); } + [Fact] + public async Task PersistStateAsync_InvokesAllCallbacksWhenFirstCallbackUnregistersItself() + { + // Arrange + var state = new Dictionary(); + var store = new TestStore(state); + var persistenceManager = new ComponentStatePersistenceManager( + NullLogger.Instance, + CreateServiceProvider()); + var renderer = new TestRenderer(); + + var executionSequence = new List(); + + // Register the first callback that will unregister itself - this is the key test case + // where the first callback removes itself from the collection during iteration + PersistingComponentStateSubscription firstSubscription = default; + firstSubscription = persistenceManager.State.RegisterOnPersisting(() => + { + executionSequence.Add(1); + firstSubscription.Dispose(); // Remove itself from the collection + return Task.CompletedTask; + }, new TestRenderMode()); + + // Register additional callbacks to ensure they still get executed + persistenceManager.State.RegisterOnPersisting(() => + { + executionSequence.Add(2); + return Task.CompletedTask; + }, new TestRenderMode()); + + persistenceManager.State.RegisterOnPersisting(() => + { + executionSequence.Add(3); + return Task.CompletedTask; + }, new TestRenderMode()); + + // Act + await persistenceManager.PersistStateAsync(store, renderer); + + // Assert + // All callbacks should be executed even though the first one removed itself + Assert.Contains(1, executionSequence); + Assert.Contains(2, executionSequence); + Assert.Contains(3, executionSequence); + Assert.Equal(3, executionSequence.Count); + } + [Fact] public async Task RestoreStateAsync_ValidatesOnlySupportUpdatesWhenRestoreContextValueUpdate() {