Clr Object

Provides experimental access to .NET assemblies through ordinary script property, method, call and index syntax.

This class is exported by the KS module.

Note: This class is experimental: it and its nested types may change or be removed without deprecation. See #Warn Experimental. Basic values and function objects are marshalled, but complex .NET types might not convert correctly.

Load

Root := Clr.Load(AssemblyOrPath)

Loads a DLL/executable path or assembly name and returns a ManagedAssembly or ManagedNamespace wrapper. To use a library from a package feed, see #Package.

LoadPackage

Package := Clr.LoadPackage(Provider:Name , Version, Optional)

Makes a package's assemblies available at run time, and returns a ManagedAssembly over that package's own assemblies. Its types are then reachable both from the return value and through Clr by namespace. Name takes the same Provider:PackageId form as #Package.

Version takes the same forms as the #Package directive. If Optional is true, a package or separately installed provider that cannot be made available returns an empty string instead of raising an error. Malformed package or version syntax remains an error when the provider is installed.

Note: Prefer #Package where it fits. A new request resolves every package requested so far, including those named by #Package; repeating an already loaded request with the same version, or with no version, reuses its assemblies, but an assembly which is already loaded cannot be unloaded, so a version conflict is reported rather than repaired. Use this function for a package chosen by a computed name, or one needed only on some code paths.

Note: This function needs its provider at run time. An executable built with --compile includes the provider when the compiler can tell from the call which one is used; a non-default provider named only inside a computed name must be installed beside the executable.

Invalid generic signatures raise a ValueError.

A For loop can enumerate a wrapped .NET collection, including one whose enumerator implements IEnumerator explicitly. The enumerator is disposed when enumeration ends.

Enum Values

A .NET enum value is passed as an Integer:

#Import "Ks" { Clr }
System := Clr.Load("System")
System.IO.File.SetUnixFileMode(Path, 0o600)

The value does not have to be a declared member, so a flag combination can be built in script with |. The member itself works equally well when read through a ManagedType, as in System.StringComparison.OrdinalIgnoreCase.

An enum returned from .NET arrives as a wrapper; use ToString for the member name. A non-numeric value passed as an enum raises a TypeError.

Byte Arrays and Spans

A Buffer or any object exposing numeric Ptr and Size properties passed where .NET expects a byte[] parameter, property or field arrives as a copy of its bytes:

#Import "Ks" { Clr }
System := Clr.Load("System")
Buf := Buffer(3)
NumPut("UChar", 72, "UChar", 105, "UChar", 33, Buf)
MsgBox System.Convert.ToBase64String(Buf)   ; SGkh

Span<byte> and ReadOnlySpan<byte> parameters view the original storage for the duration of the call, so a mutable span can write into it directly. A byte[] overload is preferred when both forms fit.

Events

Subscription := Instance.OnEvent(EventName, Callback)
Subscription := Type.OnEvent(EventName, Callback)
Hooks := Clr.Hooks

Subscribes Callback to a .NET event of a ManagedInstance, or to a static event of a ManagedType such as Microsoft.Win32.SystemEvents, and returns a running Clr.EventSubscription. Callback is a function object which receives the event's own arguments, typically (Sender, EventArgs). OnEvent takes precedence over a member of that name which the .NET type declares itself.

OnEvent takes exactly two arguments; any other number raises a ValueError. Calling an event's compiler-generated add_EventName or remove_EventName accessor raises a MethodError naming OnEvent. An EventName the target does not declare raises a ValueError.

An event raised on the thread which subscribed runs the callback at once, and only then can the callback's return value or error reach the .NET code which raised it. An event raised on any other thread is queued and runs in its own thread on the one which subscribed.

The subscription is an EventHook, with these members of its own:

MemberDescription
EventNameThe name of the event it is attached to.
TargetThe ManagedInstance or ManagedType it is attached to.

Start() after Stop() attaches a new handler, which may run in a different position among the event's other handlers. Stopping, lifetime and Clr.Hooks work as described under EventHook.

A subscription does not keep the script running, so a script which only waits for .NET events calls Persistent.

#CSharp
public class Raiser
{
    public event System.EventHandler Fired;
    public void Raise() => Fired?.Invoke(this, System.EventArgs.Empty);
}

public static Raiser MakeRaiser() => new Raiser();
#EndCSharp

R := MakeRaiser()
Sub := R.OnEvent("Fired", (Sender, Args) => MsgBox("Fired"))
R.Raise()   ; raised on the script's own thread, so the callback runs at once
Sub.Stop()
R.Raise()   ; the subscription has ended, so nothing is shown

Name Helpers

Name := Clr.GetNamespaceName(ManagedNamespace)
Name := Clr.GetTypeName(ManagedType)

Returns the underlying full namespace or type name.

Wrap

Wrapped := Clr.Wrap(Value)

Returns any value as an ordinary Clr object, with the member-access semantics described above, so the value's own full CLR surface is reachable late-bound. Map and Array are themselves CLR objects — a Map is a CLR IDictionary<object, object> and an Array an IList, so a CLR API declaring one accepts them directly — and wrapping one reaches those members from script.

The result is always a view over Value itself. To reach the .NET object a built-in is a facade over — the toolkit window behind a Gui, the HttpClient behind an Http session — call that type's own ToClr().

Wrapping something which is already a Clr object is the identity. Wrapping a ComObject yields a view over its wrapper object, not the COM interface.

The last line of this example works only on Windows.

#Import "Ks" { Clr }
arr := [1, 2, 3]
MsgBox Clr.Wrap(arr).Count            ; the CLR IList surface of the Array itself
MyGui := Gui(, "Demo")
MsgBox MyGui.ToClr().Text             ; Windows: the title of the toolkit window the Gui is a facade over

Inline C# (#CSharp) holding a ManagedInstance reads the raw wrapped object from its Native property. Script member access on a wrapper always dispatches to the wrapped object, so Native is unreachable from script.

Examples

#Import "Ks" { Clr }
System := Clr.Load("System")
Linq := System.Linq.Enumerable
Odds := Linq.Where([1, 2, 3, 4], n => Mod(n, 2))