{"record":{"id":"9855093fb146540e","repo":"stride3d/stride","slug":"this-view-model-has-already-been-disposed","errorCode":null,"errorMessage":"This view model has already been disposed.","messagePattern":"This view model has already been disposed\\.","errorType":"exception","errorClass":"ObjectDisposedException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs","lineNumber":60,"sourceCode":"\n    /// <inheritdoc/>\n    /// <remarks>\n    /// Derived class should override this method, implement specific cleanup and then call the base implementation.\n    /// </remarks>\n    public virtual void Destroy()\n    {\n        IsDestroyed = true;\n    }\n\n    /// <summary>\n    /// Checks whether this view model has been disposed, and throws an <see cref=\"ObjectDisposedException\"/> if it is the case.\n    /// </summary>\n    /// <param name=\"name\">The name to supply to the <see cref=\"ObjectDisposedException\"/>.</param>\n    protected void EnsureNotDestroyed(string? name = null)\n    {\n        if (IsDestroyed)\n        {\n            throw new ObjectDisposedException(name ?? GetType().Name, \"This view model has already been disposed.\");\n        }\n    }\n\n    /// <summary>\n    /// Sets the value of a field to the given value. Both values are compared with the default <see cref=\"EqualityComparer{T}\"/>, and if they are equals,\n    /// this method does nothing. If they are different, the <see cref=\"PropertyChanging\"/> event will be raised first, then the field value will be modified,\n    /// and finally the <see cref=\"PropertyChanged\"/> event will be raised.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of the field.</typeparam>\n    /// <param name=\"field\">A reference to the field to set.</param>\n    /// <param name=\"value\">The new value to set.</param>\n    /// <param name=\"propertyName\">The name of the property that must be notified as changing/changed. Can use <see cref=\"CallerMemberNameAttribute\"/>.</param>\n    /// <returns><c>True</c> if the field was modified and events were raised, <c>False</c> if the new value was equal to the old one and nothing was done.</returns>\n    protected bool SetValue<T>([NotNullIfNotNull(nameof(value))] ref T field, T value, [CallerMemberName] string propertyName = null!)\n    {\n        return SetValue(ref field, value, null, [propertyName]);\n    }\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs#L42-L78","documentation":"Thrown by ViewModelBase.EnsureNotDestroyed when a member of an already-destroyed view model is accessed. View models implement Destroy for cleanup and set IsDestroyed; methods call EnsureNotDestroyed (passing optionally their name for the ObjectDisposedException) to refuse further use after destruction. This fires when editor code keeps a reference to a view model after its owning window/asset editor has closed and calls into it.","triggerScenarios":"Calling any protected API that calls EnsureNotDestroyed after the view model's Dispose has been invoked — e.g. touching properties, services, or commands of a disposed view model.","commonSituations":"Event handlers or async continuations firing after a view model was disposed (window/editor closed); long-lived references to view models kept after their owner was destroyed.","solutions":["Check IsDestroyed before using the view model, or wrap usage in a lifetime check","Unsubscribe handlers and cancel async work when the view model is disposed","Fix the ownership/lifetime mismatch so the view model is not used after disposal"],"exampleFix":"// before\nviewModel.SomeProperty = x; // may throw if disposed\n// after\nif (!viewModel.IsDestroyed) viewModel.SomeProperty = x;","handlingStrategy":"validation","validationCode":"if (viewModelBase.IsDestroyed)\n    return; // skip the update; view model already disposed","typeGuard":"bool IsAlive(ViewModelBase vm) => !vm.IsDestroyed;","tryCatchPattern":"try { vm.DoWork(); }\ncatch (ObjectDisposedException) { /* view model already disposed; ignore or unsubscribe */ }","preventionTips":["Unsubscribe from events and cancel async work in Dispose","Hold view models via weak references or lifetime-scoped containers","Gate UI updates on IsDestroyed before touching a view model"],"tags":["dotnet","mvvm","disposed","viewmodel"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}