stride3d/stride · error · ObjectDisposedException
This view model has already been disposed.
Error message
This view model has already been disposed.
What it means
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.
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
Example fix
// before viewModel.SomeProperty = x; // may throw if disposed // after if (!viewModel.IsDestroyed) viewModel.SomeProperty = x;
Defensive patterns
Strategy: validation
Validate before calling
if (viewModelBase.IsDestroyed)
return; // skip the update; view model already disposed Type guard
bool IsAlive(ViewModelBase vm) => !vm.IsDestroyed;
Try / catch
try { vm.DoWork(); }
catch (ObjectDisposedException) { /* view model already disposed; ignore or unsubscribe */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- This method must be invoked with at least one property name.
- Can not convert value to the required type
- The provided path is not a valid path name.
- Build manifest [{0}] doesn't exist
- This tool requires an input file (package, project, or .sdbu
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/9855093fb146540e.
Report an issue: GitHub.
Appendix: source
Thrown at sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs:60
/// <inheritdoc/>
/// <remarks>
/// Derived class should override this method, implement specific cleanup and then call the base implementation.
/// </remarks>
public virtual void Destroy()
{
IsDestroyed = true;
}
/// <summary>
/// Checks whether this view model has been disposed, and throws an <see cref="ObjectDisposedException"/> if it is the case.
/// </summary>
/// <param name="name">The name to supply to the <see cref="ObjectDisposedException"/>.</param>
protected void EnsureNotDestroyed(string? name = null)
{
if (IsDestroyed)
{
throw new ObjectDisposedException(name ?? GetType().Name, "This view model has already been disposed.");
}
}
/// <summary>
/// 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,
/// this method does nothing. If they are different, the <see cref="PropertyChanging"/> event will be raised first, then the field value will be modified,
/// and finally the <see cref="PropertyChanged"/> event will be raised.
/// </summary>
/// <typeparam name="T">The type of the field.</typeparam>
/// <param name="field">A reference to the field to set.</param>
/// <param name="value">The new value to set.</param>
/// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
/// <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>
protected bool SetValue<T>([NotNullIfNotNull(nameof(value))] ref T field, T value, [CallerMemberName] string propertyName = null!)
{
return SetValue(ref field, value, null, [propertyName]);
}
View on GitHub (pinned to 96fad776d2)