{"record":{"id":"514ccb6e1b1bed6b","repo":"sschmid/Entitas","slug":"entityisalreadyretainedbyownerexception-entity-owner","errorCode":null,"errorMessage":"EntityIsAlreadyRetainedByOwnerException(_entity, owner)","messagePattern":"EntityIsAlreadyRetainedByOwnerException\\(_entity, owner\\)","errorType":"exception","errorClass":"EntityIsAlreadyRetainedByOwnerException","httpStatus":null,"severity":"error","filePath":"src/Entitas/Entity/SafeAERC.cs","lineNumber":31,"sourceCode":"    public sealed class SafeAERC : IAERC\n    {\n        public static readonly Func<Entity, IAERC> Delegate = entity => new SafeAERC(entity);\n\n        public int RetainCount => _owners.Count;\n        public HashSet<object> Owners => _owners;\n\n        readonly Entity _entity;\n        readonly HashSet<object> _owners = new HashSet<object>();\n\n        public SafeAERC(Entity entity)\n        {\n            _entity = entity;\n        }\n\n        public void Retain(object owner)\n        {\n            if (!Owners.Add(owner))\n                throw new EntityIsAlreadyRetainedByOwnerException(_entity, owner);\n        }\n\n        public void Release(object owner)\n        {\n            if (!Owners.Remove(owner))\n                throw new EntityIsNotRetainedByOwnerException(_entity, owner);\n        }\n    }\n}\n","sourceCodeStart":13,"sourceCodeEnd":41,"githubUrl":"https://github.com/sschmid/Entitas/blob/37547d1bd280f3f1c1ef7edb44f2a206660fbfb4/src/Entitas/Entity/SafeAERC.cs#L13-L41","documentation":"Thrown by SafeAERC.Retain when the given owner already retains the entity. SafeAERC (safe automatic entity retention control) tracks a set of owners; Owners.Add returns false if the owner is already present, which means Retain was called twice for the same owner — an unbalanced retain that would leak the entity.","triggerScenarios":"Calling entity.Retain(owner) twice with the same owner object without an intervening Release(owner); retaining in a handler that can fire repeatedly for the same entity.","commonSituations":"System retaining entities on every OnEntityAdded event including duplicates; re-entrant initialization running Retain twice; retaining in both a constructor and an OnEntityAdded handler.","solutions":["Track retention yourself: only call Retain if the entity was not already retained by that owner","Call Release(owner) symmetrically in the corresponding teardown/callback path","Retain once at acquisition (e.g. when adding to your collection) and release at removal","Switch to UnsafeAERC only if you fully control retention and accept no safety checks"],"exampleFix":"// before\nvoid OnEntityAdded(Entity e) { e.Retain(this); _list.Add(e); } // duplicates possible\n// after\nvoid OnEntityAdded(Entity e) {\n    if (!_list.Contains(e)) {\n        e.Retain(this);\n        _list.Add(e);\n    }\n}","handlingStrategy":"validation","validationCode":"if (entity.Aerc is SafeAERC sa && !sa.Owners.Contains(owner)) entity.Retain(owner);","typeGuard":"bool CanRetain(Entity e, object owner) => e.Aerc is SafeAERC sa ? !sa.Owners.Contains(owner) : true;","tryCatchPattern":"try { entity.Retain(owner); }\ncatch (EntityIsAlreadyRetainedByOwnerException) { /* already retained by this owner */ }","preventionTips":["Retain exactly once per owner, at acquisition time","Keep Retain/Release symmetric in add/remove pairs","Deduplicate events before retaining in handlers"],"tags":["csharp","entitas","retention","aerc"],"backgroundTag":"internal-invariant-violation","analyzedSha":"37547d1bd280f3f1c1ef7edb44f2a206660fbfb4","analyzedAt":"2026-09-14T00:26:23.246Z","contentChangedAt":"2026-09-14T00:26:23.246Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}