Class NDScopedExitAttribute
- Namespace
- NumSharp
- Assembly
- NumSharp.dll
Marks a by-value parameter as one the callee RETAINS — a reference it keeps past the call (stores in a field/property, adds to a long-lived collection, captures in a closure/task that outlives the call). At build time the NumSharp IL weaver detaches the argument from whatever NDScope tracks it, so the CALLER's scope will NOT reclaim an NDArray the callee is still holding.
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class NDScopedExitAttribute : Attribute
- Inheritance
-
NDScopedExitAttribute
- Inherited Members
Remarks
The hazard it closes. A NDScope disposes every array constructed
under it that was not yielded via Returns<T>(T) — regardless of any
external reference. So a NDScopedAttribute method that hands a freshly built
array to something that KEEPS it (rather than merely reading it) would have that array
reclaimed at scope exit while the retainer still points at it — a use-after-free. Marking the
retaining parameter [NDScopedExit] makes the callee remove the argument from the
ambient scope, so it survives (falling to the ordinary finalizer backstop unless the retainer
disposes it — survival, not eager reclamation).
How the weave works. The attributed method is rewritten to call
Detach(NDArray) (or the NDArray-array /
ITuple overload) on the parameter at the START
of the visible method body — for an async/iterator method that is the compiler STUB,
which runs synchronously on the caller's thread at the call, exactly where the ambient scope
is the caller's. Because NDScope is [ThreadStatic] and the callee runs
within the caller's scope, Detach reaches into the caller's scope with no argument
re-plumbing at the call site. It is a no-op when the argument is untracked (no ambient scope,
or the array was constructed outside one), so an [NDScopedExit] method is always safe
to call.
What "any kind of setter / method" covers. A property setter is a method whose
value parameter can carry the attribute ([param: NDScopedExit]), so
obj.Prop = a is covered; likewise any method or constructor parameter you own. A RAW
public-field store (obj.field = a) has no parameter to annotate and is NOT covered —
route it through a property setter, or detach by hand with Detach(NDArray).
A parameter you cannot annotate (a BCL sink such as List<NDArray>.Add) likewise
needs a hand Detach.
Supported parameter shapes (the same NDArray-carrying shapes
Returns<T>(T) yields): NDArray / NDArray<T>,
NDArray[], and any ValueTuple/Tuple of NDArrays. Anything
else — a ref/out/in parameter, a scalar, a bare
IArraySlice/UnmanagedStorage (never scope-tracked), or an
INDArrayCarrier result struct — is a build ERROR (NDW014); detach those by hand.
Orthogonal to NDScopedAttribute/NDScopedAsyncAttribute: a
method may carry an [NDScopedExit] parameter with or without a method-level scope
attribute (a pure "retain this argument" method needs no scope of its own). A body that
already opens an NDScope by hand is left untouched (the hand author owns its
detaches too). PUBLIC because the attribute is consumer-facing; without the
NumSharp.Build package it is inert metadata (the argument is not detached — the
pre-weave behaviour, where a caller must avoid handing a scoped temp to a retainer).
Inherited by overrides and implementations, by parameter position. On a
virtual, abstract or interface declaration the attribute is the contract: an override or
implementation that declares no [NDScopedExit] parameter of its own takes the nearest
such declaration's set (the same override/implementation walk the scope attributes use), so
"this parameter is retained" is stated once, where the API is declared. An override that
marks ANY parameter itself uses only its own marks. (Inherited = true documents the
intent; reflection ignores the flag for parameters, and the weaver/analyzer implement the
inheritance themselves.)