{"record":{"id":"cfea31d2a458ea4c","repo":"stride3d/stride","slug":"cannot-add-a-reference-for-an-object-already-released","errorCode":null,"errorMessage":"Cannot add a reference for an object already released. AddReference/Release pair must match.","messagePattern":"Cannot add a reference for an object already released\\. AddReference/Release pair must match\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core/DisposeBase.cs","lineNumber":69,"sourceCode":"    ///     This method is automatically called whenever a call to <see cref=\"Dispose\"/> (or to <see cref=\"IReferencable.Release\"/>)\n    ///     has decreased the internal reference count to zero, meaning no other objects (hopefully) hold a reference to this one\n    ///     and its resources can be safely released.\n    ///   </para>\n    /// </remarks>\n    protected virtual void Destroy() { }\n\n\n    /// <inheritdoc/>\n    int IReferencable.ReferenceCount => refCount;\n\n    /// <inheritdoc/>\n    int IReferencable.AddReference()\n    {\n        OnAddReference();\n\n        var newCounter = Interlocked.Increment(ref refCount);\n        if (newCounter <= 1)\n            throw new InvalidOperationException(FrameworkResources.AddReferenceError);\n\n        return newCounter;\n    }\n\n    /// <inheritdoc/>\n    int IReferencable.Release()\n    {\n        OnReleaseReference();\n\n        var newCounter = Interlocked.Decrement(ref refCount);\n        if (newCounter == 0)\n        {\n            Destroy();\n            IsDisposed = true;\n        }\n        else if (newCounter < 0)\n        {\n            throw new InvalidOperationException(FrameworkResources.ReleaseReferenceError);","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core/DisposeBase.cs#L51-L87","documentation":"Stride.Core reference-counted objects (DisposeBase implementing IReferencable) maintain an internal refCount. AddReference increments it atomically and throws InvalidOperationException when the counter is at or below 1 after incrementing, meaning the object has already been fully released/destroyed and cannot be resurrected. The AddReference/Release pairs must be balanced; adding a reference to a released object indicates a use-after-release bug.","triggerScenarios":"Calling IReferencable.AddReference() on an object whose refCount has already dropped to 0 (or gone negative) — i.e. after the final Release() destroyed it. Also happens when an unbalanced Release brought the counter to 0 and other components still hold what they believe are live references.","commonSituations":"Graphics resource lifetime bugs (textures, buffers, effects) where a resource was released but still referenced by a render pipeline; double-release caused by both manual Dispose/Release and GC-driven disposal; caching a released module and trying to reload it via AddReference.","solutions":["Audit AddReference/Release call sites to ensure every AddReference has exactly one matching Release","Stop using the object after the final Release; reacquire it from its source (e.g. content manager) instead of calling AddReference","Debug refCount transitions with the OnAddReference/OnRelease overrides or a debugger breakpoint on DisposeBase to find the extra Release","Use EnsureLifetime or a keep-alive reference if the object must outlive certain subsystems"],"exampleFix":"// before\neffect.Release();\n// ... later, elsewhere ...\neffect.AddReference(); // InvalidOperationException: object already released\n// after\nif (!effect.IsDisposed && effect.RefCount > 0)\n{\n    effect.AddReference();\n}\nelse\n{\n    effect = Content.Load<Effect>(\"my-effect\"); // reacquire instead of resurrecting\n}","handlingStrategy":"validation","validationCode":"if (obj is IReferencable r && !((DisposeBase)obj).IsDisposed)\n{\n    r.AddReference();\n}\nelse\n{\n    // reacquire the object from its owner/content manager\n}","typeGuard":"static bool CanAddReference(DisposeBase obj) =>\n    obj is IReferencable && !obj.IsDisposed && obj.RefCount > 0;","tryCatchPattern":"try\n{\n    ((IReferencable)obj).AddReference();\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already released\"))\n{\n    // object destroyed: reacquire from source instead of using this instance\n}","preventionTips":["Always pair every AddReference with exactly one Release on the same code path","Never call Release in both Dispose and a manual cleanup path for the same ownership","Check IsDisposed before touching reference-counted objects","Use EnsureLifetime or a permanent keep-alive reference for shared resources"],"tags":["csharp","reference-counting","object-lifetime","invalid-operation"],"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"}