dotnet/runtime · error · Error
NotImplementedException ${jsType}. ${jsinteropDoc}
Error message
NotImplementedException ${jsType}. ${jsinteropDoc} What it means
Thrown by marshalCsObjectToCs when a value carries the jsOwnedGcHandleSymbol (so it is treated as a C#-roundtripped object) but its runtime type is neither ManagedError, ManagedObject, nor ArraySegment. The .NET JS interop marshaler only knows how to send these specific managed proxy shapes back to C#, so any other prototype that happens to carry the gcHandle symbol is rejected as not implemented. The message includes the JS typeof string and a link to the jsinterop docs.
Source
Thrown at src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-cs.ts:414
if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
value[proxyDebugSymbol] = `JS Object with JSHandle ${jsHandle}`;
}
setJsHandle(arg, jsHandle);
} else {
throw new Error(`JSObject proxy is not supported for ${jsType} ${value}`);
}
} else {
assertNotDisposed(value);
if (value instanceof ArraySegment) {
throw new Error("NotImplementedException: ArraySegment. " + jsinteropDoc);
} else if (value instanceof ManagedError) {
setArgType(arg, MarshalerType.Exception);
setGcHandle(arg, gcHandle);
} else if (value instanceof ManagedObject) {
setArgType(arg, MarshalerType.Object);
setGcHandle(arg, gcHandle);
} else {
throw new Error("NotImplementedException " + jsType + ". " + jsinteropDoc);
}
}
}
}
export function marshalArrayToCs(arg: JSMarshalerArgument, value: Array<any> | TypedArray | undefined | null, elementType?: MarshalerType): void {
dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
marshalArrayToCsImpl(arg, value, elementType);
}
export function marshalArrayToCsImpl(arg: JSMarshalerArgument, value: Array<any> | TypedArray | undefined | null, elementType: MarshalerType): void {
if (value === null || value === undefined) {
setArgType(arg, MarshalerType.None);
} else {
const elementSize = arrayElementSize(elementType);
dotnetAssert.fastCheck(elementSize != -1, () => `Element type ${elementType} not supported`);
const length = value.length;
const bufferLength = elementSize * length;View on GitHub (pinned to 60108ba66e)
Solutions
- Pass the original ManagedObject/ManagedError instance directly instead of a wrapper or copy; do not transplant the gcHandle symbol onto other objects.
- If you subclassed ManagedObject, ensure instanceof checks still hold (do not override Symbol.hasInstance / prototype chains) or unwrap to the base instance before the interop call.
- Rebuild and redeploy the dotnet wasm runtime and JS loader from the same source/commit so the ManagedObject class identity used by instanceof matches at runtime.
- Audit the call site with a debugger break at marshal-to-cs.ts:414 to confirm the value's prototype and that value[jsOwnedGcHandleSymbol] is genuinely expected.
Example fix
// before
const wrapped = Object.assign({}, csharpObject); // carries jsOwnedGcHandleSymbol via copy?
exportToCSharp(wrapped); // throws NotImplementedException object.
// after
exportToCSharp(csharpObject); // pass the original ManagedObject instance Defensive patterns
Strategy: type-guard
Validate before calling
import { ManagedObject, ManagedError, ArraySegment } from 'System.Runtime.InteropServices.JavaScript.Native/interop/core';
function isMarshalableCsObject(v: unknown): boolean {
if (v === null || v === undefined) return true; // None path
if (!(typeof v === 'object')) return false;
const hasGc = (v as any)[jsOwnedGcHandleSymbol] !== undefined;
if (!hasGc) return true; // non-gcHandle branch handles primitives/arrays/etc.
return v instanceof ManagedObject || v instanceof ManagedError || v instanceof ArraySegment;
}
if (!isMarshalableCsObject(arg)) throw new TypeError('arg is not a marshalable managed proxy'); Type guard
function isManagedObjectLike(v: unknown): v is ManagedObject | ManagedError | ArraySegment {
return v instanceof ManagedObject || v instanceof ManagedError || v instanceof ArraySegment;
} Try / catch
try {
exportToCSharp(maybeProxy);
} catch (e) {
if (e instanceof Error && e.message.startsWith('NotImplementedException ')) {
throw new TypeError('Passed a non-managed object to C# interop; unwrap to the original ManagedObject.', { cause: e });
}
throw e;
} Prevention
- Never copy/transplant the jsOwnedGcHandleSymbol onto other objects.
- Pass ManagedObject/ManagedError/ArraySegment instances directly, not wrappers.
- Keep the dotnet wasm runtime and the JS loader on the same build so instanceof checks hold.
When it happens
Trigger: Passing a value to a C# [JSExport] argument where the value has the jsOwnedGcHandleSymbol property but is an instance of a custom/extension class rather than ManagedObject/ManagedError/ArraySegment (e.g. an object that wrapped or cloned a managed proxy, or a ManagedObject subclass the marshaler does not recognize). Reached whenever marshalCsObjectToCs takes the gcHandle !== undefined branch at marshal-to-cs.ts:403 and falls through all instanceof checks to line 414.
Common situations: Manually copying properties off a ManagedObject onto a plain object (which then inherits/retains the symbol), subclassing ManagedObject in user JS interop code, passing a stale proxy whose prototype was swapped, or using reflection-like wrappers around C# objects. Also seen when a NuGet dotnet wasm build is mismatched with the JS runtime so proxy class identity checks (instanceof ManagedObject) fail.
Related errors
- not implemented
- NotImplementedException ${elementType}. ${jsinteropDoc}
- NotImplementedException ${elementType}
- NotImplementedException: bigint
- NotImplementedException: TypedArray
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/3f3747874adcb224.
Report an issue: GitHub.