Calling .NET from Dyalog APL: When the Bridge Is Worth the Toll
Dyalog's .NET interop lets APL sessions load assemblies and call C# code directly — but runtime-version mismatches and marshaling overhead decide whether the bridge is worth crossing.
08 Sept 2026, 20:47 UTC

You have a fast APL session, a library that only exists as a .NET assembly, and a deadline. Dyalog's .NET interop lets you load that assembly and call its types directly from the workspace — no wrapper process, no file-based handoff. The catch is that the bridge has a toll: data conversion cost, runtime-version constraints, and a few failure modes that only show up at load time. This piece walks through when the interop pays off and how to verify it works before you build on it.
What the interop actually gives you
Dyalog exposes .NET through system functions and commands in the session — most notably ⎕NEW for constructing .NET objects and the ⎕NET mechanism for loading assemblies. Once an assembly is loaded, its types behave like first-class citizens: you can instantiate classes, call static methods, and read properties using ordinary APL syntax.
The data mapping is automatic for the common cases. APL numbers map to .NET numeric primitives, APL vectors map to .NET arrays, and strings cross in both directions without ceremony. Where it gets awkward is deeply nested or heterogeneous structures — a nested APL array does not always have an obvious .NET counterpart, and you may need an explicit conversion step on one side of the call to avoid a runtime type error.
A concrete scenario: a C# solver called from APL
The classic justification is a performance-critical kernel that already exists in C# — say, a numerical solver tuned for large matrices. Rewriting it in APL is possible but wasteful; calling it is cheap. The shape of the interaction looks like this in a Dyalog session:
⎕NET'load' 'C:\libs\Solver.dll'
s ← ⎕NEW Solver.Engine
result ← s.Run matrixRun this in the APL session (the REPL), not from a shell. No special OS permissions are needed beyond read access to the DLL, but the assembly must target a .NET runtime your Dyalog build actually supports — more on that below. If result comes back as the expected numeric array with no error, both the load and the marshaling path are working. If the load fails, the first thing to check is runtime targeting, not your APL syntax.
The version question you cannot skip
For years, Dyalog's .NET interop meant .NET Framework on Windows. Newer releases — the research brief for this piece flags Dyalog 18.2 as the inflection point — added support for .NET Core and the modern .NET line (6, 8, and later). That matters because an assembly compiled for .NET Framework will not necessarily load into a session running on .NET 8, and vice versa. Load failures from a runtime mismatch can be unhelpfully generic, so this is worth ruling out first.
Two verification steps before you commit:
- Confirm your interpreter version. In the session, check the version information Dyalog reports (for example via
⎕DT-adjacent system queries or the About dialog), then read the release notes for that exact build to see which .NET runtime it hosts. Do not assume — the same major Dyalog version can ship in different runtime flavors. - Smoke-test with a trivial assembly you control: one static method that returns an integer. If
⎕NEWor the load call throws, you have a runtime or targeting problem. If it returns the integer, your real assembly's failure is in its own dependencies.
One caveat to flag for review: exact command names and the set of supported runtimes vary by Dyalog version and platform, and this area has moved quickly. Treat the release notes for your installed build as the authority, not any blog post — including this one.
The trade-off: marshaling overhead
Every call across the boundary converts data. For a single call that crunches a large matrix for two seconds, the conversion cost is noise. For a tight APL loop that calls a .NET function ten thousand times with small arguments, the overhead can easily exceed the work being done — native APL would win. The rule of thumb: cross the boundary few times with large payloads, not many times with small ones. If you find yourself wrapping a .NET call inside ¨ (each), that is a design smell; push the loop into the C# side instead.
There is also a deployment cost. Your application now depends on both the Dyalog runtime and a specific .NET runtime being present and compatible on the target machine. That is a real operational burden compared to a pure-APL solution, and it deserves a line in your runbook.
Deciding whether to use it
Reach for the interop when the .NET side offers something APL genuinely lacks — an existing library, a platform API, or a measured performance win on large data. Stay in APL for small, chatty helper functions and for anything you can express idiomatically in a line or two. Whichever you choose, write the smoke test first: load the assembly, call the simplest method, and check the returned type and shape with ⎕DR and ⍴. Five minutes of verification up front beats an afternoon of chasing a runtime mismatch through a production workspace.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.