stride3d/stride · error · InvalidOperationException
Cannot add a reference for an object already released…
Error message
Cannot add a reference for an object already released. AddReference/Release pair must match.
What it means
ReferenceBase.AddReference atomically increments the reference counter; a correct AddReference/Release sequence never lets the counter reach 0 while references are still being added. Interlocked.Increment returning <= 1 means the counter was already 0 or negative — the object was released — so InvalidOperationException (FrameworkResources.AddReferenceError) is thrown to flag a mismatched reference-count lifecycle.
Solutions
- Fix ownership so every AddReference has exactly one matching Release (avoid Release without ownership).
- Re-acquire/reload the resource instead of adding a reference to a released instance.
- Audit disposal order so objects are not released while still referenced elsewhere.
- Check ReferenceCount > 0 (or a Disposed flag) before calling AddReference in defensive code.
Example fix
// before texture.Release(); ... texture.AddReference(); // throws: already released // after: keep it alive while you still need it if (texture.ReferenceCount > 0) texture.AddReference(); else texture = LoadTexture(name); // re-acquire
Defensive patterns
Strategy: try-catch
Validate before calling
if (obj.ReferenceCount <= 0 || obj.IsDisposed)
throw new InvalidOperationException("Object already released; cannot AddReference"); Type guard
static bool IsAlive(ReferenceBase r) => r.ReferenceCount > 0;
Try / catch
try { obj.AddReference(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already released"))
{
// re-acquire the resource instead of resurrecting the released instance
} Prevention
- Enforce strict AddReference/Release pairing per owner.
- Never call Release on objects you do not own; never AddReference after releasing.
- Clear cached references when objects are disposed so stale reuse is impossible.
- Prefer using blocks/scopes for deterministic lifetimes instead of manual counting.
When it happens
Trigger: Calling AddReference() on an IReferencable object after its reference count already dropped to 0 (Release brought it to zero and the object was considered released).
Common situations: Double-Release on one code path then AddReference from another holder; caching a released GraphicsResource/texture and reusing it next frame; resurrection after a using/Dispose block; passing a disposed object across threads that already finalized its last reference.
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
- Cannot add a reference for an object already released…
- Cannot release an object that doesn't have active…
- Cannot release an object that doesn't have active…
- Cannot query package with dependencies when it is not…
- [ . ] must be in
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/4a7513f1b79c1dbc.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core/ReferenceBase.cs:19
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
namespace Stride.Core;
/// <summary>
/// Base class for a <see cref="IReferencable"/> class.
/// </summary>
public abstract class ReferenceBase : IReferencable
{
private int counter = 1;
/// <inheritdoc/>
public int ReferenceCount { get { return counter; } }
/// <inheritdoc/>
public virtual int AddReference()
{
var newCounter = Interlocked.Increment(ref counter);
if (newCounter <= 1) throw new InvalidOperationException(FrameworkResources.AddReferenceError);
return newCounter;
}
/// <inheritdoc/>
public virtual int Release()
{
var newCounter = Interlocked.Decrement(ref counter);
if (newCounter == 0)
{
try
{
Destroy();
}
finally
{
// Reverse back the counter if there are any exceptions in the destroy method
Interlocked.Exchange(ref counter, newCounter + 1);
}View on GitHub (pinned to 96fad776d2)