dotnet
experimentalTyped C# extern functions, async tasks, callbacks and live .NET objects through dotbridge.
use plugin dotnet Plugin snippets can be edited in the Playground, but native capabilities are executed locally with the Zolo CLI.
Functions (6)
From import to result
- 01Import only what you use
- 02Copy the smallest example
- 03Run it with the Zolo CLI
Typed C# functions¶
use plugin dotnet::{csharp}
extern csharp fn add(a: int, b: int) -> int {
return a + b;
}
extern csharp async fn twice(value: int) -> int {
await System.Threading.Tasks.Task.Delay(10);
return value * 2;
}
print(add(20, 22))
print(await twice(21))csharp is an imported provider. The function signature uses Zolo types and the
body uses C#. Calls behave like ordinary Zolo functions. int is an exact CLR
long, i32 is int, and bytes is byte[]. Arrays preserve nil slots and
empty sequences; Zolo structs become copied DTOs. Async functions return normal
Futures and permit callbacks to reenter Zolo on the owning thread.
Native imports can appear at the beginning of a function or expression block:
extern csharp fn byte_count(text: str) -> int {
using System.Text;
return Encoding.UTF8.GetByteCount(text);
}Namespace imports, aliases and using static use C# syntax and are scoped to
the binding. They precede executable statements; resource usings keep their
ordinary scope and disposal. Roslyn diagnostics, completion and hover retain
the original source positions, including within imports. A using directive
selects names from available references; it does not install dependencies.
extern csharp fn apply(value: int, transform: fn(int) -> int) -> int {
return transform(value);
}
print(apply(21) { |value| value * 2 })
extern csharp fn counter(start: int) -> fn() -> int {
var count = start;
return () => System.Threading.Interlocked.Increment(ref count);
}
let next = counter(10)
print(next()) // 11
print(next()) // 12extern csharp mod declares native classes. extern csharp from imports source,
DLL or project types with reflected signatures. Imported objects retain state
and identity. Use their original CLR member spelling and dispose resources with
defer object.Dispose(). Relative import paths follow the source file. Project
imports require an SDK and locked dependencies; inline functions use embedded
Roslyn. The runtime requires .NET 8+ and supports VM, native and LLVM targets.
Hosted providers produce an explicit unsupported-target error on wasm.
The following dynamic API keeps its original object-based conversion rules.
Compile C# once and return a reusable function
use plugin dotnet
let add = dotnet.compile(r#"async (input) => { return (int)input + 1; }"#)
defer add.close()
print(add.call(41)) // 42Use raw strings (r#"..."#) to keep C# braces and quotes literal.
Accepts an async C# lambda or a public Startup class with Invoke(object).
Extra DLL paths participate in compilation and runtime dependency resolution.
Compile a UTF-8 C# source file
Reads UTF-8 C# source and compiles it. Instance fields persist across calls.
Compile and execute C# once
use plugin dotnet
print(dotnet.eval(r#"async (dynamic input) => { return input.name; }"#, #{name: "Zolo"}))Compiles, calls and releases a function. For repeated calls, use compile.
Load a public method from a .NET DLL
use plugin dotnet
let twice = dotnet.load("./Example.dll", "Example.Startup", "Double")
defer twice.close()
print(twice.call(21))The public instance or static method accepts one object and returns object
or Task<object>. On the VM, paths starting with ./ or ../ follow the caller's
source directory; AOT executables use the working directory. An assembly-qualified
type name is also accepted.
Passes one value; omitted input becomes null.
Passes one value; omitted input becomes null. Arrays retain empty sequences
and nil slots, bytes become byte[], and string-keyed maps become dynamic
objects. Calls are serialized and block until C# tasks complete.
Releases the managed handle.
Releases the managed handle. Subsequent calls fail. .NET exceptions, including
available stack traces, propagate through Zolo's try/catch channel.
NuGet dependencies for extern C#¶
use plugin dotnet::{csharp}
extern csharp deps {
"MathNet.Numerics" = "5.0.0"
}
extern csharp fn factorial(n: int) -> float {
using MathNet.Numerics;
return SpecialFunctions.Factorial(checked((int)n));
}Alternatively, use [foreign.dotnet.dependencies] in zolo.toml, with
[packages] schema = 2. zolo install resolves NuGet into an isolated, locked
environment before Roslyn compilation. Short strings select exact NuGet
versions. Explicit requirement values retain NuGet's native range syntax.
Compile references, runtime assemblies and selected native assets are captured
in the portable extern artifact.
The default framework is net8.0, with the current host RID.
ZOLO_DOTNET_FRAMEWORK and ZOLO_DOTNET_RID select compatible targets.
--offline --frozen restores only the matching locked closure from local caches.
This dependency workflow applies to typed extern bindings; the legacy
DotNetFunction API retains its own dynamic input contract.
See foreign dependencies.
Requirements and limits¶
Build with .NET SDK 8+. Run with the same-architecture .NET runtime 8+. The plugin embeds its managed support libraries and works with VM and native or LLVM executables. CoreCLR is unavailable in browsers and wasm-aot.
Signed 32-bit integers become C# int; larger integers within ±2^53 become
double. Larger inputs are rejected; pass strings for long.Parse or
decimal.Parse. C# long/ulong results use the upstream double wire format:
return strings when exact large values matter. Decimal and Guid return strings;
DateTime returns Unix milliseconds. Non-string map keys and callbacks are
unsupported.
Set DOTNET_ROOT for custom runtime installations. DOTBRIDGE_BOOTSTRAP_DIR
overrides the embedded bootstrap; DOTBRIDGE_DEBUG enables diagnostics.