title: "C# Foundations — Compile Rules, TK Toolkit, and Runtime Collection Access" tags: [csharp, vb, scripts, codebehind, tk, reference, foundations] description: "Reference for the FX-specific C# patterns: @ namespaces, compile subset rules, TK toolkit index, T.Modules cast catalog, runtime collection access, and Tag default-Value semantics." version: "1.0" author: "Tatsoft"
Reference for the FX-specific C# patterns that diverge from textbook .NET: the @ namespaces, the compile-subset rules, the TK toolkit index, the T.Modules cast catalog, runtime collection access patterns, and Tag default-Value semantics. This is a lookup reference, not a how-to walkthrough. Companions:
Use when:
@-prefix rule are in mind@-prefixed identifier, "dynamic" rejection)ListObj collection or looking up by nameTK toolkit helper and needing the surface indexDo NOT use for:
Twelve runtime namespaces are reachable from any script body using the @ prefix. The @ is mandatory — without it, the compiler treats the identifier as a local variable and rejects member access.
Namespace |
What It Holds |
Where It Runs |
|---|---|---|
|
Tag values, qualities, timestamps, members |
Server tasks and CodeBehind |
|
Global server runtime properties |
Server only |
|
Solution metadata, build info, platform paths |
Both |
|
Alarm groups, items, areas |
Server only |
|
Client session: navigation, dialogs, query params, log-on |
CodeBehind / client tasks only |
|
Users, identity providers, sessions, policies |
Most accessors server-only; |
|
Display registry, layouts |
Both |
|
Script registry — reach Class methods via |
Both |
|
Query and table results, DB providers |
Server only |
|
Historian groups and tags |
Server only |
|
Report forms, web-data forms |
Server only |
|
Device channels, nodes, points |
Server only |
Execution-scope decision rule. Picking the wrong scope fails at runtime, not compile-time — the compiler accepts the call; the runtime rejects it.
@Alarm, @Device, @Dataset, @Historian, @Report, @Server): callable from Tasks, Classes (Server domain), Expressions. Calling from CodeBehind fails.@Client): callable from CodeBehind and client-domain Classes. Calling from a Server Task fails.@Tag, @Info, @Display, @Script, @Security): work from either context, with minor caveats per namespace.FrameworX scripting compiles a subset of .NET. Five constraints fail at compile or behave differently from textbook .NET:
dynamic keyword is not supported. The compiler treats dynamic as object and rejects member access. Always use explicit as <FullyQualifiedType> casts (see Runtime Collection Access for the canonical pattern).@ prefix is mandatory. When accessing FX runtime objects in code, the prefix is required. Without it the compiler treats the identifier as a local variable and CS0103 / CS0117 surface.@Tag.X = 5 writes the value. @Tag.X = @Tag.Y copies the current value (not a reference, not a live binding). Other namespaces have no default property — @Server.*, @Alarm.*, @Display.*, @Script.* all require explicit property names.ListObj collections needs an explicit cast per element. Bare foreach (var x in @Alarm.Group) x.ActiveCount fails — the loop variable is the base type and the member is not visible. See Runtime Collection Access below for the cast pattern.async/await is supported in Tasks and CodeBehind handlers, NOT in ScriptsExpressions. Expressions are single-line VB.NET expressions — use synchronous patterns. The wrapper makes Task / CodeBehind bodies await-able implicitly.The single most common first-pass error is putting a class, namespace, method declaration, or using / Imports declaration inside Contents for the wrapped shapes. The FX compiler wraps your body in a generated class at compile time — with one exception.
Shape |
What |
Wrapped? |
|---|---|---|
|
Method body only — raw statements ( |
Yes |
|
Method members only ( |
Yes |
|
A FULL |
No — the lone exception |
|
Method members of the generated Display class — lifecycle methods and click handlers |
Yes |
|
A single VB.NET expression that returns the value to assign to |
Yes |
Where do using declarations go? For wrapped shapes (everything except ClassContent = Namespace), put them in the NamespaceDeclarations field on the row — semicolon-separated, e.g. System.Linq;System.Text;T.Toolkit.LocalAI. Never inline inside Contents — the wrapper rejects them.
Diagnostic to recognize. Putting a method declaration like public void Initialize() { ... } inside a wrapped Contents fails with the misleading CS0106 "modifier public is not valid for this item". That is the wrapper rejecting a nested method — the fix is to remove the method-declaration wrapper from your body, not to remove the public modifier.
Runtime collections like @Security.IdentityProviders, @Alarm.Group, @Security.User, @Device.Channels are typed ListObj<T>. Three access patterns cover every case.
LINQ (.First(), .Where()) does not work — dynamic is unsupported, so you must cast explicitly:
foreach (var item in @Security.IdentityProviders) {
var idp = item as T.Modules.Security.SecurityIdentityProvider;
if (idp == null) continue;
if (idp.Active && idp.AuthType == eIdentityProviderAuthType.OIDC) {
// use idp.Name, idp.Authority, idp.ClientId, etc.
}
}
Always null-check the as cast — it returns null rather than throwing.
The indexer returns the typed row directly. Returns null when the named row does not exist — null-check before accessing properties:
@Security.IdentityProviders["Keycloak"].Authority
@Alarm.Group["Critical"].ActiveCount
@Security.User["Operator1"].UserPermission
The full T.Modules.<Module> namespace is required because Display and Script CodeBehind compile without using T.Modules.* by default. Common row classes:
Namespace |
Row classes |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
TK is the script toolkit class, reached directly from any script body as TK.X(...) — no @Module prefix. It hosts the canonical script-API surface for tag historian queries, tag and UDT operations, object-name resolution, UNS walk helpers, type conversion, and diagnostics.
In 10.1.5 the surface is organized into capability groups: TK.Data, TK.Convert, TK.Run, TK.History, TK.Visual, TK.Util. New code uses these grouped forms. The legacy flat TK.<method> shape remains supported for backward compatibility — existing customer scripts compile unchanged.
Bucket |
What It Covers |
|---|---|
|
Tag I/O ( |
|
Type conversion ( |
|
Expression engine ( |
|
Tag historian query ( |
|
Image-bytes-to-shape rendering ( |
|
Diagnostics ( |
|
UNS table walk — |
|
Runtime Reference traversal — |
|
Local AI atomic LLM calls — |
The legacy TK.AIExecute shim is deprecated and surfaces one obsolete warning per call site — new code uses AI.Execute from T.Toolkit.LocalAI.
@Tag.X resolves to @Tag.X.Value by default in assignments and expressions — @Tag is the one namespace with a default property. Read and write both read from / write to .Value.
double level = @Tag.Plant/Tank1/Level; // reads .Value
@Tag.Plant/Tank1/SetPoint = 75.0; // writes .Value
@Tag.Plant/Tank1/SetPoint = @Tag.Plant/Tank1/Level; // copies current .Value (not a binding)
To access other members, name them explicitly:
int quality = @Tag.Plant/Tank1/Level.Quality;
DateTime ts = @Tag.Plant/Tank1/Level.Timestamp;
@Tag.Plant/Tank1/Level.Forced = true;
Other namespaces (@Server.*, @Alarm.*, @Display.*, @Script.*, etc.) require explicit property names — there is no default. To get a live binding between two tags rather than a one-time copy, use a ScriptsExpressions row, not a Task assignment.
Display CodeBehind runs on the client. Lifecycle methods — declare as void members of Contents with these exact signatures:
Method |
When It Runs |
|---|---|
|
Once, before the page becomes visible — init, pre-load |
|
Periodically while open (interval = |
|
Once, when the page is closing — cleanup, save |
|
Dialog-mode display only — user clicks OK |
Inside any of these, this is the Display instance — the wrapper exposes public TDisplayControl CurrentDisplay. Reach controls placed on the display via:
var chart = this.CurrentDisplay.GetControl("<Uid>") as T.Wpf.RunControls.Charts.TTrendChart;
if (chart != null) { /* ... */ }
Controls are not auto-exposed by Name — GetControl uses the element Uid (visible in the Designer property panel). Element click handlers: declare as public void OnButtonClick() { ... } in Contents, then wire from the element via ActionDynamic { ActionType: 'RunScript', ObjectLink: 'OnButtonClick' }.
For the full Display authoring workflow, see Skill Display Construction.
Pitfall |
Symptom |
Fix |
|---|---|---|
|
CS0106 "modifier public is not valid for this item" |
Remove the method-declaration wrapper. |
Inline |
Compile fails — wrapper rejects the directive |
Move usings to the |
|
|
Capture and null-check: |
|
Compile fails — base type lacks |
Cast each iteration: |
|
LINQ on |
Iterate with |
|
Copies the current value only — not a reference |
Use a |
|
Compiler treats |
Cast explicitly: |
Calling |
Runtime fails — |
Move the call into CodeBehind, or set a tag the client watches. |
Calling |
Runtime fails — |
Move logic to a Server Task or Script Class; surface the result via a tag. |
Tag path with |
Python rejects |
Use |
|
Obsolete warning per call site |
Use |