Table of Contents

Class NDScope

Namespace
NumSharp
Assembly
NumSharp.dll

Ambient reclamation scope for transient NDArray intermediates — the library's standard way to make a composition method eagerly return its temporaries' pooled buffers instead of waiting on the finalizer (see DISPOSAL-GUIDELINES.md). Every NDArray constructed on the current thread while a scope is open is tracked by it; disposing the scope disposes every tracked array that was not yielded via Returns<T>(T). Tracked disposal is ordinary ARC release (the buffer frees only at refcount 0), so releasing a base whose view was yielded never corrupts — the same safety as a hand-written Dispose, with the bookkeeping automated.

public sealed class NDScope : IDisposable
Inheritance
NDScope
Implements
Inherited Members

Remarks

Usage. Open at the top of a boundary method, keep the original body, and route every egress through Returns<T>(T):

using var scope = NDScope.Open();
var b1 = x1.typecode == NPTypeCode.Boolean ? x1 : (x1 != 0);
var b2 = x2.typecode == NPTypeCode.Boolean ? x2 : (x2 != 0);
return scope.Returns((b1 & b2).MakeGeneric<bool>());

Ownership rules become structural. Inputs were constructed BEFORE the scope opened, so they are never tracked — a passthrough (ravel()/ atleast_2d() returning its operand, a caller-supplied @out) needs no guard, and Returns<T>(T) on such an array is a provable no-op (rule R2 for free). The return value is the one egress and is yielded (rule R1); an out-parameter egress is written as result = scope.Returns(temp);. Everything else — however deep the helper-call tree below the scope — is reclaimed, on exception paths included.

Nesting. Scopes nest per thread; Returns<T>(T) re-tracks the yielded array into the parent scope, so an enclosing scope still reclaims an inner call's result if the caller drops it.

Threading. The current scope is [ThreadStatic]: a scope is opened, used and disposed on ONE thread (asserted in debug builds; a HAND-WRITTEN scope must not span await — the weaver's state-machine seam (driven by [NDScopedAsync] for async methods/iterators and by [NDScoped] for synchronous iterators) is the one thing that may carry a scope across suspensions, because it uninstalls the scope before each continuation is scheduled and re-installs it on the resuming thread via OpenOrResume(ref NDScope)/Suspend(NDScope)). Arrays constructed on other threads (parallel kernel workers) see no scope and fall back to the finalizer backstop — safe, just not eagerly reclaimed; a parallel region that wants eager reclamation opens its own scope inside each worker body.

Granularity. Scope a CALL, not a caller loop: temps a scope holds are all alive simultaneously, and batch-disposing thousands of same-size buffers overflows their pool bucket (the excess is freed, not pooled). Hot loops should still using the results they receive — the two-audience contract is unchanged.

Methods

Attach(NDArray)

Adopts nd into the CURRENT (innermost) scope, so it is reclaimed at that scope's exit unless yielded — the inverse of Detach(NDArray). For an array the scope did not construct: one a caller received before opening its scope (the hot-loop pattern — attach instead of a per-result using), or one built on another thread and handed over. Moves the array if some other scope tracked it (the old registration is cleared first — an array is owned by at most ONE scope). No-op when no scope is open or the array is already tracked here. Must run on the thread that owns both the current scope and any previous tracking scope.

public static void Attach(NDArray nd)

Parameters

nd NDArray

CloseUnlessDeferred(NDScope)

Task-returning-method epilogue (weaver seam): the finally of a woven method whose return is Task-like — disposes unless ReturnsTask<TResult>(Task<TResult>)/ ReturnsValueTask<TResult>(ValueTask<TResult>) deferred disposal to the task's completion (the exception path never defers, so a throw still reclaims eagerly).

public static void CloseUnlessDeferred(NDScope scope)

Parameters

scope NDScope

Detach(NDArray)

Permanently removes nd from whatever scope tracks it WITHOUT re-tracking into a parent — for arrays that must outlive every scope (an array being cached into a static / long-lived field from inside a scoped call). Must run on the scope's owning thread (it always does: detachment happens at the caching site, on the constructing thread). No-op for untracked arrays. Also the runtime egress the NDScopedExitAttribute weaves at the entry of a method that RETAINS an NDArray parameter (the callee runs inside the caller's ambient scope, so this reaches into that scope with no call-site re-plumbing).

public static void Detach(NDArray nd)

Parameters

nd NDArray

Detach(NDArray[])

Detaches every element of an NDArray array — the NDArray[] egress of an NDScopedExitAttribute parameter (a callee that retains a whole tuple/list of arrays). A null array and null elements are skipped; each element is detached exactly as Detach(NDArray).

public static void Detach(NDArray[] nds)

Parameters

nds NDArray[]

Detach(ITuple)

Detaches every NDArray a tuple carries — the ValueTuple/Tuple egress of an NDScopedExitAttribute parameter, any arity (Rest-packed 8+ included) and any component mix: bare NDArrays, whole NDArray[] components, and NESTED tuples (recursively) all detach; components carrying no NDArray are skipped, and a null tuple is a no-op. Indexing through ITuple boxes value-type components, negligible on the once-per-call escape path; it mirrors Returns(ITuple).

public static void Detach(ITuple tuple)

Parameters

tuple ITuple

Dispose()

Disposes every tracked non-yielded array and reinstates the parent scope.

public void Dispose()

DisposeSlot(ref NDScope)

State-machine completion (weaver seam): disposes the slot's scope and clears the slot — the async SetResult/SetException exit, the iterator's final false, and the iterator enumerator's Dispose() (mid-iteration abandonment, which may land on a different thread than the last MoveNext; that call is sequenced after it, so the thread stamp is re-taken rather than asserted). No-op for an empty slot.

public static void DisposeSlot(ref NDScope slot)

Parameters

slot NDScope

ExitIterator(ref NDScope, bool)

Iterator-yield exit (weaver seam): a MoveNext that produced a value (hasMore) suspends the invocation scope for the next resumption; a finished one disposes it. For async iterators the weaver emits this BEFORE the promise's SetResult(hasMore) — the consumer can re-enter MoveNext the instant that signal lands, so the scope must already be off this thread.

public static void ExitIterator(ref NDScope slot, bool hasMore)

Parameters

slot NDScope
hasMore bool

Open()

Opens a scope on the current thread; nests (the previous scope resumes on dispose).

public static NDScope Open()

Returns

NDScope

OpenOrResume(ref NDScope)

State-machine prologue (weaver seam): installs the invocation's scope on the current thread — opening a fresh one into slot on the first MoveNext, and RE-INSTALLING the suspended one (re-stamping its owning thread; segments of one state machine are sequenced, so the handoff is race-free) on every resumption.

public static NDScope OpenOrResume(ref NDScope slot)

Parameters

slot NDScope

Returns

NDScope

Returns(IArraySlice)

Protects a returned bare IArraySlice from this scope's reclamation of an NDArray that shares its buffer.

public IArraySlice Returns(IArraySlice slice)

Parameters

slice IArraySlice

Returns

IArraySlice

Returns(UnmanagedStorage)

Protects a returned bare UnmanagedStorage from this scope's reclamation of an NDArray that shares its buffer.

public UnmanagedStorage Returns(UnmanagedStorage storage)

Parameters

storage UnmanagedStorage

Returns

UnmanagedStorage

Returns(ITuple)

Yields every NDArray a tuple result CARRIES, at any arity (Rest-packed 8+ included) and ANY mix — the general ITuple egress that also covers a reference-type Tuple. Each component dispatches by its RUNTIME type through the same family the weaver emits for direct returns: a bare NDArray, an NDArray[] (every element), a NESTED tuple (recursively — ((a, b), c) yields all three), an INDArrayCarrier struct (its YieldTo), and a bare IArraySlice/UnmanagedStorage (counted-ref protection). Components carrying no NDArray (a scalar, a count, …) are skipped; a null tuple is a no-op. This is the weaver's egress for a tuple the strongly-typed overloads above don't cover (arity 5+, a non-NDArray component, or a boxed/reference tuple). It indexes through ITuple, so value-type components box — negligible on the once-per-call return path; hand-scope a hot all-NDArray tuple through the typed overloads to avoid it.

public ITuple Returns(ITuple tuple)

Parameters

tuple ITuple

Returns

ITuple

ReturnsTask(Task)

Task egress without a DECLARED result (weaver seam for [NDScopedAsync] NON-async methods returning bare Task): defers this scope's disposal to the task's completion when it is still running — the in-flight work may hold tracked temps. The runtime task may still be a Task<TResult> UP-CAST to Task (Task<T> : Task is implicit, so [NDScopedAsync] Task M() => ComputeAsync(); compiles) — a caller can recover that result by casting back, so a carried result is sniffed and yielded rather than handed back disposed.

public Task ReturnsTask(Task task)

Parameters

task Task

Returns

Task

ReturnsTask<TResult>(Task<TResult>)

Yields a task-shaped result (weaver seam for [NDScopedAsync] NON-async methods returning Task<TResult>): a completed task's result is yielded immediately; an incomplete one defers BOTH the yield and this scope's disposal to the task's completion. Returns the task unchanged.

public Task<TResult> ReturnsTask<TResult>(Task<TResult> task)

Parameters

task Task<TResult>

Returns

Task<TResult>

Type Parameters

TResult

ReturnsValueTask(ValueTask)

Bare ValueTask egress (weaver seam) — the resultless twin of ReturnsValueTask<TResult>(ValueTask<TResult>).

public ValueTask ReturnsValueTask(ValueTask task)

Parameters

task ValueTask

Returns

ValueTask

ReturnsValueTask<TResult>(ValueTask<TResult>)

ValueTask<TResult> egress (weaver seam). A ValueTask is single-consumption — observing its result or registering a continuation would corrupt the caller's own await — so the scope first Preserve()s it and RETURNS THE PRESERVED task (multi-observable by contract; a plain-value or Task-backed ValueTask preserves to itself at no cost). The preserved form is then handled exactly like ReturnsTask<TResult>(Task<TResult>).

public ValueTask<TResult> ReturnsValueTask<TResult>(ValueTask<TResult> task)

Parameters

task ValueTask<TResult>

Returns

ValueTask<TResult>

Type Parameters

TResult

Returns<T>(T)

Marks nd as this scope's yielded result (rule R1: never dispose what you return): unregisters it here and re-tracks it into the parent scope, so an enclosing scope still reclaims it if the caller drops it. An array this scope never tracked — an input passthrough, a caller-owned @out — passes through as a no-op, which is what makes "wrap every egress" a safe blanket rule. Also the egress call for out-parameter assignments: result = scope.Returns(temp);.

public T Returns<T>(T nd) where T : NDArray

Parameters

nd T

Returns

T

nd, with its static type preserved.

Type Parameters

T

Returns<T>(T[])

Yields every element of a tuple-style result (nonzero, meshgrid, split, …).

public T[] Returns<T>(T[] nds) where T : NDArray

Parameters

nds T[]

Returns

T[]

Type Parameters

T

Returns<T1, T2>((T1, T2))

Yields both components of a two-array tuple result (e.g. modf, polydiv, qr, eig, slogdet, average(returned)).

public (T1, T2) Returns<T1, T2>((T1, T2) tuple) where T1 : NDArray where T2 : NDArray

Parameters

tuple (T1, T2)

Returns

(T1, T2)

Type Parameters

T1
T2

Returns<T1, T2, T3>((T1, T2, T3))

Yields all three components of a three-array tuple result (e.g. svd).

public (T1, T2, T3) Returns<T1, T2, T3>((T1, T2, T3) tuple) where T1 : NDArray where T2 : NDArray where T3 : NDArray

Parameters

tuple (T1, T2, T3)

Returns

(T1, T2, T3)

Type Parameters

T1
T2
T3

Returns<T1, T2, T3, T4>((T1, T2, T3, T4))

Yields all four components of a four-array tuple result (e.g. lstsq).

public (T1, T2, T3, T4) Returns<T1, T2, T3, T4>((T1, T2, T3, T4) tuple) where T1 : NDArray where T2 : NDArray where T3 : NDArray where T4 : NDArray

Parameters

tuple (T1, T2, T3, T4)

Returns

(T1, T2, T3, T4)

Type Parameters

T1
T2
T3
T4

Suspend(NDScope)

Uninstalls scope from the current thread WITHOUT disposing it (weaver seam): emitted immediately BEFORE the builder's Await[Unsafe]OnCompleted — once that call is made the continuation may already be resuming on another thread, so the scope must be off this thread's chain first. Everything tracked stays alive until the state machine completes. Also the deferral seam of ReturnsTask<TResult>(Task<TResult>). No-op for null/disposed.

public static void Suspend(NDScope scope)

Parameters

scope NDScope