C# deletion contracts
The design guidelines keep storage and application policies separate.
Both UnitedMemoryLinks<T> and SplitMemoryLinks<T> expose unchecked deletion.
Choose a check per call or an outer decorator when addresses may be missing.
The runnable example checks the principal
recipes below with both heap-backed stores:
dotnet run --project examples/Deletion -c Release
API and overload contracts
Import Platform.Data, Platform.Data.Doublets, and
Platform.Data.Doublets.Decorators for the helpers and decorators.
| Call on an undecorated store | What it does | Required caller guarantees |
|---|---|---|
Delete(new[] { id }, handler) |
Release the slot; return handler flow or Continue |
Live address, endpoints already reset, incoming usages handled |
Delete(id) |
Platform.Data address adapter; return deleted address from callback |
Same preconditions as the inner deletion; no automatic reset or missing check |
Delete(id, handler) |
Reset endpoints when Exists(id), then call inner deletion |
Address still must exist; missing addresses are forwarded; incoming usages handled separately |
EnsureLinkExists(id, name) |
Throw ArgumentLinkDoesNotExistsException<T> if missing |
No deletion; caller chooses the subsequent operation |
DeleteIfExists(source, target) |
Search by pair; delete only if found; return address or Null |
Endpoint reset and incoming-usage policy supplied separately |
Delete(id) and Delete(id, null) select different helpers. Do not infer safety
from the method name alone. A handler overload returns a flow constant, not the
deleted address. An ignored deletion produces no callback, so the address-returning
adapter returns Null while the low-level policy returns Continue.
Caller-managed fast path
Use a valid live address, detach its endpoints, and resolve incoming usages in your application before releasing it. An isolated point has no other users:
var id = raw.CreatePoint();
raw.Update(id, 0u, 0u);
raw.Delete(new[] { id }, null);
The memory backend performs no existence validation for this deletion. Never use
repeated deletion of a raw address as a membership test. Zero, service constants,
external references, freed slots and unallocated addresses do not satisfy the
live-link precondition. AreValuesReset and EnforceResetValues also require an
existing link; they are not missing-link validators.
Checks for one call
For a missing-address exception, validate before resetting or deleting:
raw.EnsureLinkExists(id, nameof(id));
raw.Delete(id, null); // This helper supplies endpoint reset.
For an address-based no-op, skip the whole operation when absent:
if (raw.Exists(id))
{
raw.Delete(id, null);
}
The existing pair-based DeleteIfExists(source, target) already skips a failed
search. Wrap the raw store in NonNullContentsLinkDeletionResolver<T> to supply
resetting before using that helper on links with non-null contents. A successful
search alone does not reset endpoints or handle incoming usages.
Store-wide policies
Use the existing validator to reject missing deletion requests:
ILinks<uint> links = new LinksInnerReferenceExistenceValidator<uint>(
raw.DecorateWithAutomaticUniquenessAndUsagesResolution());
It also validates internal references in Each and Update. If that broader
contract is unsuitable, choose per-call EnsureLinkExists, or implement a
focused LinksDecoratorBase<T>.Delete override for your application.
Use the optional ignore policy for idempotent deletion requests:
ILinks<uint> links = new LinksNonExistentDeletionResolver<uint>(
raw.DecorateWithAutomaticUniquenessAndUsagesResolution());
It checks existence, returns Continue without inner writes or callbacks for a
missing address, and forwards an existing deletion unchanged. It does not add
resetting, cascading, uniqueness or update validation. To reset without cascading,
wrap raw in NonNullContentsLinkDeletionResolver<uint> instead of the automatic
composition in these snippets.
Place the chosen missing-address policy outside reset and cascade layers.
Otherwise a reset resolver may read a missing link before the guard sees it.
The automatic composition itself only resolves uniqueness, contents and usages;
it does not promise missing-address validation. Adding a policy decorator updates
the C# Facade, so recursive cascade calls re-enter the selected outer layers.
If you compose both throw and ignore policies, the outer policy decides which
missing requests reach the inner policy.
Incoming usages and bulk operations
Existence does not decide what to do with other links referencing the deleted link. Choose one of these contracts:
- Manage usages yourself before raw deletion, for example by removing or re-pointing references according to your data model.
- Reject incoming usages with
LinksUsagesValidator<T>. A checked reset-only composition, outermost first, isLinksInnerReferenceExistenceValidator→NonNullContentsLinkDeletionResolver→LinksUsagesValidator→ raw store. Self references are excluded from the usages check. - Cascade usages with
DecorateWithAutomaticUniquenessAndUsagesResolution()or the explicitNonNullContentsLinkDeletionResolver(LinksCascadeUsagesResolver(raw))stack, and place the missing-address policy outside it. Cascading deletes other links too; C# cycle/recursion behavior is not strengthened by this PR.
DeleteMany, DeleteByQuery, DeleteAllUsages, DeleteAll, and MergeAndDelete
are compound helpers, not universal validators. Apply the chosen contract to
every inner deletion, and account for links already removed by cascades. For
bulk deletion with holes, enumerate a snapshot of actual addresses, then delete
through the ignore policy (or test Exists before each call). The executable
example demonstrates this. The old DeleteAll count-based loop has a separate
known issue;
MergeUsages also has a separate known issue.
NoExceptionsDecorator suppresses exceptions; it does not undo earlier writes
and cannot make an invalid unchecked deletion safe. Callback Break affects
handler flow after mutation; it is not a transaction rollback. Existence checks
are not synchronization: callers must prevent conflicting concurrent writes
and must not mutate the store unexpectedly from callbacks between steps.