Table of Contents

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.)