C# update contracts
UnitedMemoryLinks<T> and SplitMemoryLinks<T> update a live address under
caller-managed preconditions. Missing-address validation is an optional policy,
as described in the shared design. The automatic uniqueness/usages
composition does not select that policy. A returned address does not establish
that an unchecked request was valid.
For issue #517, the existing validator and per-call helpers provide the requested checked boundary. No new decorator or compulsory backend check is needed. See the investigation and evidence and runnable recipes.
Choose an update contract
Import Platform.Data, Platform.Data.Doublets, and
Platform.Data.Doublets.Decorators for these APIs.
| Choice | Missing internal address | Other responsibilities |
|---|---|---|
| Raw store or automatic uniqueness/usages composition | Outside the caller contract; no guaranteed exception or result | Caller guarantees a live update address and valid endpoints/index state |
Outer LinksInnerReferenceExistenceValidator<T> |
Throws ArgumentLinkDoesNotExistsException<T> before forwarding |
Validates every internal reference in both restriction and substitution, and also affects reads/deletion |
EnsureLinkExists(id, nameof(id)) before updating |
Throws before updating | Checks one reference; endpoint policy remains separate |
if (links.Exists(id)) before updating |
Skips the complete update | Caller chooses how to represent the skipped result |
Application-specific LinksDecoratorBase<T>.Update override |
Whatever the application explicitly selects | Guard before forwarding; use a focused wrapper when the broader validator contract is unsuitable |
The update address must be a live internal link. Null, Any, Itself, other
service constants and external values cannot identify a raw slot to update.
EnsureInnerReferenceExists and its validator only check values classified as
internal references: they do not turn arbitrary numeric inputs into valid update
addresses. Exists treats configured external references as existing values,
so EnsureLinkExists alone also does not prove an external value is a live slot.
For untrusted address input, first require Constants.IsInternalReference(id):
if (!links.Constants.IsInternalReference(id))
throw new ArgumentOutOfRangeException(nameof(id));
links.EnsureLinkExists(id, nameof(id));
links.Update(id, source, target);
That range check concerns the update address. Null endpoints can be useful for resetting; supported external endpoints depend on the configured backend. The inner-reference validator permits such non-internal endpoint values; it does not enforce a particular application's external-reference rules.
Store-wide rejection before resolver work
Put the validator outside the complete resolver stack:
ILinks<uint> links = new LinksInnerReferenceExistenceValidator<uint>(
raw.DecorateWithAutomaticUniquenessAndUsagesResolution());
links.CreatePoint();
links.CreatePoint();
links.Update(10u, 1u, 2u); // Throws before search, reset, merge or memory update.
For a plain store, construct new LinksInnerReferenceExistenceValidator<uint>(raw).
The same boundary rejects a freed middle slot before its free-list metadata can
be interpreted as a live link. It also rejects missing internal source/target
references and internal references in a low-level substitution. If only address
validation is wanted, use the per-call recipe or a focused application wrapper.
Wrapping raw with the validator and then adding resolvers outside it does not
provide this ordering: uniqueness can search or merge before the check is reached.
C# decorators propagate the outer Facade for recursive resolver operations;
validate at the entry boundary, and use that boundary consistently.
DecorateWithAutomaticUniquenessAndUsagesResolution() composes, outermost first,
LinksCascadeUniquenessAndUsagesResolver, NonNullContentsLinkDeletionResolver,
and LinksCascadeUsagesResolver. Their names describe resolution policies,
not existence guarantees. A deletion ignore policy likewise does not guard updates.
Per-call checks and missing-address no-ops
When id is already known to be an internal address, choose a throwing check:
links.EnsureLinkExists(id, nameof(id));
links.Update(id, source, target);
Or skip a missing update entirely:
if (links.Constants.IsInternalReference(id) && links.Exists(id))
{
links.Update(id, source, target);
}
The skipped branch performs no update, resolver search, mutation or callback.
Return a separate status or a chosen application value if a result is required.
Do not fabricate a successful updated address. NoExceptionsDecorator suppresses
exceptions but does not undo earlier writes or validate unsafe requests.
Caller-managed updates and overload results
For a valid live link, raw updates retain the route without existence validation:
var first = raw.CreatePoint();
var second = raw.CreatePoint();
raw.Update(second, first, second);
The current layout requires coherent index/free-list metadata, a live internal update address, and endpoints that the selected indexing strategy supports. For split internal indexing, use live internal endpoint links (or null to reset); external endpoints require the configured external range. Updating a freed slot or an unallocated address violates these preconditions and may corrupt indexes, allocation or memory, or hang. The observed outcomes in the issue are not a supported missing-address policy. Do not probe invalid raw writes in-process.
| Overload | Result |
|---|---|
Update(id, source, target) |
Address adapter: derives its result from the write callback |
Update(id, source, target, handler) |
Inner flow result, such as Continue or Break |
Update(restriction, substitution, handler) |
Primitive flow result; null handler does not invoke a callback |
Update(restriction, substitution) |
Address adapter around the primitive |
Use a single concrete address in primitive restrictions and keep the substitution
index consistent with that address. The raw implementation does not implement a
general query/update language. Convenience Update overloads taking two or four
values can select compound merge or update/create/get operations; they are not
missing-address validators. Uniqueness resolution can merge/delete links, so
the result and callbacks follow the selected stack's behavior.
Break is callback flow after a write; it does not roll the write back. Guarding
existence is not synchronization. Prevent concurrent deletion/reuse and unexpected
callback mutation between checking and updating. A reused numeric address names
its new live link; these checks do not supply generation/version identity.