Table of Contents

Class NDScopedAttribute

Namespace
NumSharp
Assembly
NumSharp.dll

Marks a method (or a property accessor) as an NDScope boundary: at build time the NumSharp IL weaver (tools/NumSharp.Build, shipped to consumer projects as the NumSharp.Build NuGet package) injects the exact code the hand-written pattern spells —

using var scope = NDScope.Open();
...original body, byte-for-byte...
return scope.Returns(result);        // NDArray-like returns

— so the source keeps its 100% original body and the reclamation is invisible.

[AttributeUsage(AttributeTargets.Method|AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NDScopedAttribute : Attribute
Inheritance
NDScopedAttribute
Inherited Members

Remarks

What the weaver injects (see DISPOSAL-GUIDELINES.md → "The weaver"): a scope local assigned from Open() before the original first instruction; the whole original body wrapped in try/finally with Dispose() in the finally; every ret routed through Returns<T>(T) for NDArray-like returns (Returns<T>(T[]) for array returns, the typed Returns tuple overloads for a small all-NDArray ValueTuple and Returns(ITuple) for any other ValueTuple/Tuple — any arity up to 8, mixed components — Returns(IArraySlice)/ Returns(UnmanagedStorage) for a bare lower-layer buffer return, and YieldTo(NDScope) for a result-struct carrier), and every out NDArray parameter's final value yielded before each successful return.

Synchronous iterators are woven too — an IEnumerable<T>/ IEnumerator<T> method (yield return without await) compiles to a state machine, so the stub keeps only the attribute and the scope is held in a weaver-added state-machine field: ONE scope for the whole enumeration, suspended between MoveNext calls and reclaimed at the final MoveNext or the enumerator's Dispose() (an early break out of a foreach included), with every yield returned element routed through the same Returns/YieldTo egress the consumer owns.

Async methods, async iterators and non-async Task/ValueTask returns use NDScopedAsyncAttribute instead — those suspend across await (or defer disposal to a task's completion) and need the deferral seam. Marking one of them [NDScoped] is a build ERROR (NDW009) that names the right attribute, never a silent unwoven ship.

The weaver REJECTS (build error NDW003) only shapes whose egress it cannot see: ref NDArray parameters, and an UNSUPPORTED carrier — a bespoke reference type, a collection, or a result struct that does NOT implement INDArrayCarrier — whether returned directly or yield returned; scope those by hand (NDW004 = an unrecognized, non-C# state-machine shape; NDW008 = the referenced NumSharp predates the iterator scope seam).

A method whose body already opens an NDScope is skipped (idempotence), so hand-scoped code may carry the attribute without double-wrapping.

On a virtual, abstract or interface member the attribute is a CONTRACT its inheritors inherit. Every override and every implementation (implicit or explicit, through a generic base or interface, in this assembly or in a consumer assembly overriding a NumSharp member) is woven exactly as if it carried the attribute itself — the declaration decides the scoping, the implementation keeps its 100% original body. The nearest declaration up the override/implementation chain wins, and an override that declares its OWN scope-family attribute keeps it: [NDScoped]/[NDScopedAsync] re-state or change the model, NDScopedCoveredAttribute opts the override OUT of the inherited weave (its transients then ride the caller's ambient scope — the author's assertion, as for any covered helper). A body-less attributed declaration (abstract method, interface member, abstract property) is never itself woven and never an error — NDW005 is reserved for an extern, which has neither a body nor inheritors. The compile-time analyzer applies the same resolution: an inheriting override draws no NDW012 for its transients, and its target gate (NDW002–NDW011) reads its declaration's attribute. Inherited = true below states the same for reflection (GetCustomAttribute(inherit: true) finds the base declaration's attribute on an override); the weaver and analyzer implement the inheritance themselves — they read metadata, not reflection.

PUBLIC because the attribute is consumer-facing: a project that installs the NumSharp.Build package marks its own composition methods with it. Without the package the attribute is inert metadata — the method runs unscoped, the pre-weave finalizer-backstop behaviour.