eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilation
Supported C# Features Matrix
Edit this page
12 min read
🌐 This page in: English · Português
This document provides a comprehensive list of C# features, .NET APIs, and patterns supported by the eQuantic.UI compiler.
LEGENDFull Support: Transpiles to equivalent JavaScript behavior. ⚠️ Partial Support: Works with caveats or minor differences. ❌ Not Supported: fundamentally incompatible logic (e.g., blocking I/O, unsafe pointers).
🏗️ Core Language Features
Feature
Status
Notes
Classes & Structs
Transpiled to ES6 Classes.
Interfaces
Used for TypeScript type checking (erased at runtime).
Enums
Cross as their camelCase name (MainAlign.Start'start'). .ToString() and interpolation give the C# member name ("Start"), the same text the server prints. Since 0.2.0-preview.15.
Generics
Fully supported (erased at runtime).
Extension Methods
Resolved via Semantic Model and transpiled to direct calls.
Async / Await
Maps to async / await and Promise.
Lambda Expressions
Maps to Arrow Functions () => {}.
Pattern Matching
is patterns, Property Patterns, Recursive Patterns.
String Interpolation
Maps to Template Literals ${var} .
Null-Coalescing
?? and ??= mapped to JS equivalents.
Object Initializers
new Obj { Prop = 1 }.
field (C# 14)
Guarded properties: the twin gets a $name slot (a name no C# field can take), a getter and the accessor's own body.
init accessors
Emitted as a JS setter, because an init body is where a type states its invariant, and dropping it would lose the check on the client.
Collection Initializers
new List<int> { 1, 2 } maps to [1, 2].
Deconstruction
var (a, b) = tuple maps to [a, b] = tuple.
Local Functions
Transpiled to inner functions.
Records (with)
Maps to object spread { ...src, prop: val }.
typeof
Maps to type name string literal.
base calls
Maps to super keyword.
cast & as
Maps to JS passthrough/truncation.
sizeof
Maps to C# primitive sizes.
Anonymous Methods
delegate(...) { ... } maps to arrow functions.
params
A REST parameter (...xs). Both C# call forms work: expanded arguments and an array passed whole, which spreads. Since 0.2.0-preview.15.
stackalloc
Maps to Typed Arrays (e.g., Int32Array).
yield return
Maps to JS Generator Functions (function*).
lock
⚠️
Transpiled to a no-op block (JS is single-threaded).
🔄 LINQ Support
The compiler includes specialized strategies for nearly all LINQ methods.
Logic
Methods
Status
Filtering
Where, OfType
Projection
Select, SelectMany, Cast
Partitioning
Skip, Take, SkipWhile, TakeWhile
Ordering
OrderBy, OrderByDescending, ThenBy, Reverse
Aggregation
Count, Sum, Min, Max, Average, Aggregate
Quantifiers
Any, All, Contains
Sets
Distinct, DistinctBy, Union, Intersect, Except, Concat
Elements
First, FirstOrDefault, Single, Last, ElementAt
Utility
SequenceEqual, DefaultIfEmpty
Conversion
ToList, ToArray, ToDictionary, ToHashSet
Grouping/Join
GroupBy, Join, Zip
📦 .NET Types (BCL)
We map common .NET types to their JavaScript equivalents.
Primitives
.NET Type
JavaScript Equivalent
string
String
int, double, float
Number
bool
Boolean
object
Object
dynamic
any
System.String
Join, Format
IsNullOrEmpty, IsNullOrWhiteSpace
Split, Replace, Substring, Trim
ToLower, ToUpper, StartsWith, EndsWith
System.DateTime, TimeSpan, DateOnly, TimeOnly & DateTimeOffset
The temporal types are backed by tick-precise compat types (100-ns ticks, proleptic Gregorian calendar, not the lossy new Date() / numeric-milliseconds mapping). Ctors, components, Add*, arithmetic (-TimeSpan), comparisons, and invariant .ToString() all match .NET; values cross the SSR wire as ISO-8601 / "c" strings and are hydrated back into the compat type. See the .NET BCL Coverage & Conformance table below for the per-type detail.
System.Collections.Generic
List<T> → Javascript Array []
Dictionary<TKey, TValue> → plain Object {} for string/number/enum keys; $eq.collections.valueMap for record/struct/tuple keys (structural-equality keys, so two equal-by-value keys collide as in .NET).
HashSet<T> → Javascript Set.
ContainsKey / TryGetValue ask for the object's own key, so a dictionary never answers for "constructor", "toString" or anything else on Object.prototype. Since 0.2.0-preview.15.
Queue<T>, Stack<T>, LinkedList<T>, SortedSet<T>, SortedDictionary<K,V>, SortedList<K,V> → runtime compat collections under $eq.collections.*.
System.Threading.Tasks
Task, Task<T>Promise.
Task.DelaysetTimeout wrapper.
Task.WhenAll, Task.WhenAny.
⚠️ Task.Run executes on the main thread (microtask), NOT a background thread.
Other Utilities
Console.WriteLineconsole.log.
Math.* (Min, Max, Abs, Round, etc.) → Math.*.
Guid (NewGuid, Empty, Parse) → crypto.randomUUID().
Regex → JavaScript RegExp.
🌐 Ecosystem Packages
eQuantic.UI.Lucide / Heroicons / RadixIcons / TablerIcons / Phosphor / SimpleIcons / BootstrapIcons / Iconoir / ...
Purpose: Comprehensive Icon Sets. Contains:
SVG resolution logic
Specialized icon components
IIconProvider implementation
Package
Purpose
Status
eQuantic.UI.Charts (Apex/ChartJS)
High-performance visualization.
eQuantic.UI.Lottie
High-performance animations.
eQuantic.UI.Image
Optimized (Lazy/Blur/Priority).
eQuantic.UI.Tailwind
Standard styling integration.
🧩 Framework Patterns
Pattern
Interface
Description
Status
Metadata Management
IHandleMetadata
SEO tags and head elements.
Asset Management
IRequireAssets
Dynamic script/style injection.
Server Actions
[ServerAction]
Secure RPC from Client to Server.
Compound Components
N/A
Semantic sub-component patterns.
⚠️ Limitations & Caveats
1.
Blocking Code:
.Wait(), .Result on Tasks are NOT supported. You must use await. Blocking the main thread freezes the browser UI.
2.
Reflection:
System.Reflection is largely unsupported.
typeof(T).Name and nameof(...) are supported constants.
3.
File System:
System.IO (File, Directory) is not strictly forbidden but will fail at runtime in the browser.
Use Server Actions to handle file operations.
4.
Numbers:
int/double/float map to JS numbers. Integer division truncates (Math.trunc) and Math.Round uses banker's rounding (MidpointRounding.ToEven) via the round compat helper.
decimal is exact end-to-end: compiled to the runtime Decimal compat type (BigInt mantissa + scale), so 0.1m + 0.2m == 0.3m is true. Decimals also cross the wire as **JSON strings (EqJson) and are hydrated back into Decimal** on the client (the field's Decimal default drives a type-preserving coercion, see hydrate-value.ts), so server-provided decimals keep all 28 digits instead of rounding through a double.
long/ulong are now exact: compiled to JS BigInt via the long compat helper. 9007199254740993L + 1L is 9007199254740994 (a plain JS number would round to …992). Literals become BigInt (5L5n); arithmetic/comparison operands are wrapped in long() (which coerces number/stringbigint) so mixed expressions never throw. On the wire, 64-bit ints cross as JSON strings (Server Actions + SSR state, via EqJson) so values beyond 2^53 survive the round trip. Other numeric type suffixes (1.5f, 100u) are stripped.
5.
Thread Safety:
Since JS is single-threaded, lock statements are compiled away (ignored).
Thread.Sleep is not supported (use Task.Delay).
🧪 .NET BCL Coverage & Conformance
Transpilation fidelity is enforced by a conformance harness (tests/eQuantic.UI.Conformance.Tests): each case runs the same C# expression two ways, transpiled to JS (executed via the embedded Bun) and evaluated directly in .NET (Roslyn scripting), and asserts identical results. 460+ cases are green.
(Most recent: record-keyed dictionaries via $eq.collections.valueMap.) This covers both expressions and statement blocks (control flow: if/for/foreach/while/switch/ try-catch-finally/local functions; the block runs in an IIFE and its returned value is compared).
Every construct resolves via one of three mechanisms (see docs/DOTNET-COVERAGE-PROGRAM.md):
1.
Native strategy: idiomatic JS when the runtime has an equivalent.
2.
.NET-compat runtime helper: faithful .NET semantics where JS has none. The transpiler emits these under a single namespace $eq (organised by domain), brought in with **one import per module**, import { $eq } from "@equantic/runtime" (resolved by the page's import map), instead of N loose helper imports, and $eq.* can never collide with a user identifier in the generated scope:
$eq.num: dec (exact Decimal), long (Int64 via BigInt)
$eq.math: round (banker's rounding)
$eq.text: format (number/string formatting), stringBuilder
$eq.time: dateTime, timeSpan, dateOnly, timeOnly, dateTimeOffset
$eq.enums: parse (enum member-name)
$eq.collections: queue (FIFO), stack (LIFO), valueMap (structurally-keyed dictionary), linkedList, sortedSet/sortedDictionary/sortedList (key-sorted)
$eq.nullable: arith, cmp (lifted Nullable<T> operators: null-propagating arithmetic, false-on-null relational)
$eq.equals: structural (value) equality for records/structs/tuples (backs ==, .Equals, Contains, Distinct)
$eq.css: styleBuilder, classBuilder, joinClasses, whenClass (the styling subsystem)
3.
Fail-on-unsupported: a construct with no possible JS representation now raises a build error (with a stable EQ code) instead of being silently emitted verbatim. Two layers:
UnsupportedConstructStrategy (EQ2001): typed-reference intrinsics (__makeref, __refvalue, __reftype), pointer types, function pointers.
goto/goto case/goto default (EQ2002): no JS equivalent; restructure with loops/conditionals. (unsafe/fixed/lock blocks unwrap to their body, lock being a single-threaded no-op, and a bare label drops to its inner statement.)
SemanticValidator client/server boundary (EQ21xx): calls into System.IO, System.Net.Http, System.Net.Sockets, EF Core / System.Data, OS threading (Thread/Monitor/Mutex), Process, InteropServices (P/Invoke), Reflection.Emit from a client component. (System.Threading.Tasks is not forbidden: async maps to Promise.) The fix is to move the call into a [ServerAction].
Any other construct that hits no strategy is emitted verbatim but now reported as a warning (EQ1001/EQ1002) so it is visible rather than silent. Diagnostics print in MSBuild-canonical form, so errors fail dotnet build.
Area
Status
Notes
Arithmetic / bitwise / comparison
integer division truncates; %, shifts, & | ^ ~ native; checked(expr) throws OverflowException, unchecked(expr) wraps to 32 bits (\| 0/>>> 0). Default-context int overflow does not wrap (JS float64).
Math.*
Truncatetrunc, Ceilingceil, Round banker's via round helper
decimal
exact via Decimal (literals + + - * / == != < > <= >=)
Numeric constants
int.MaxValue, double.Epsilon, … → literals
Parsing / Convert.*
int/double.Parse, bool.Parse, Convert.ToInt32/ToDouble/ToString/ToBoolean/…
Strings
Substring/IndexOf/Replace/Split/Pad/Trim(char)/Concat/Format/Join/IsNullOrEmpty/IsNullOrWhiteSpace; StringComparison-aware Equals/StartsWith/EndsWith/Contains/IndexOf (Ordinal + IgnoreCase). Culture-sensitive ordering (CompareTo) is out of scope.
char.*
ToUpper/ToLower/IsDigit/IsLetter/IsWhiteSpace/… (Unicode-aware)
StringBuilder
compat type: Append(incl. bool→"True"/"False")/AppendLine("\n")/Insert/Remove/Replace/Clear/Length/ToString
LINQ
Where/Select/SelectMany/Where-Select(indexed)/OrderBy/Distinct(By)/GroupBy/ToDictionary/ToLookup/Zip/Chunk/MinBy/MaxBy/Take(While)/Skip(While)/Aggregate/Sum/Min/Max/Average/Count/Any/All/First/Last/Concat/Reverse/Join/GroupJoin/ThenBy/ThenByDescending (Join/GroupJoin = order-preserving hash join over primitive keys; OrderBy+ThenBy = single stable composite sort, source copied). IGrouping from GroupBy/ToLookup is usable as a sequence (iterate, g.Select/g.Sum/g.Count()) and exposes g.Key; ILookup [key] indexer is not modelled.
Collections
List, Dictionary, HashSet (incl. initializers and .Count); Queue/Stack/LinkedList compat; sorted family SortedSet/SortedDictionary/SortedList (key-sorted enumeration). Record/struct/tuple-keyed dictionaries route to $eq.collections.valueMap (structural keys: construction, d[k] get/set, ContainsKey/Add/Remove/Clear/TryGetValue/GetValueOrDefault, Keys/Values/Count, foreach); string/number/enum-keyed dictionaries keep the plain-object form. ILookup[key] indexer returns the group (or empty for an absent key).
enum
member-name string (equality/switch/ternary)
long/ulong
exact via BigInt (long helper); literals 5L5n, wire as JSON string
DateTime
tick-precise DateTime compat type: ctors, components, Add*, -TimeSpan, comparisons, .ToString()/format; ISO-8601 wire + hydration
TimeSpan
tick-precise TimeSpan compat type: From*, ctors, components/totals, + -, comparisons, .NET "c" .ToString(); "c" wire + hydration
DateOnly / TimeOnly
compat types (.NET 6+): ctors, components, Add*(+ TimeOnly wrap), comparisons, invariant .ToString() (MM/dd/yyyy / HH:mm); ISO wire + hydration
DateTimeOffset
tick-precise compat type (wall-clock + offset, compared by the instant): ctors, components, Offset/UtcDateTime/LocalDateTime, ToOffset, Add*, From/ToUnixTime*, - TimeSpan, instant comparisons, invariant .ToString() (MM/dd/yyyy HH:mm:ss zzz); ISO+offset wire + hydration
record / struct / value tuple
Value semantics. Records/structs are plain objects (positional new Point(1,2){x,y}, object initializers merge), tuples are arrays with element access by position (t.Item1) and by declared name ((int X, int Y).X) → index. ==/!=, .Equals, Contains, Distinct compare structurally via $eq.equals; with copies-and-replaces. Deconstruction var (a, b) = … works for tuples (array destructuring, discard holes) and records (object destructuring by Deconstruct order). Records emit as named JS classes carrying their user instance methods + a structural equals, prototype-preserving with, and .NET toString; value semantics are unchanged. The build pipeline discovers records by scanning and emits each as its own module; components that reference a record import it automatically (reactive, no hardcoded list). SSR values are re-hydrated back into the record class on the client (recursively, restoring nested records and compat members), so methods/instanceof survive. Covers positional and body records and plain structs (object-initializer construction maps to the constructor by member order, with per-member defaults), plus record inheritance (extends + super) and generic records (type args erased). Record-keyed dictionaries (Dictionary<RecordKey, V>) route to $eq.collections.valueMap so equal-by-value keys collide as in .NET.
Nullable<T> (T?)
HasValue/Value, GetValueOrDefault() (type-aware default: 0/false/$eq.num.dec(0)/enum zero-member/…) and GetValueOrDefault(fallback), ??; lifted operators via $eq.nullable.*: arithmetic propagates null, relational (< > <= >=) is false when either side is null (not a numeric coercion). No-arg GetValueOrDefault() on DateTime?/Guid?/struct yields null, so use the fallback form there.
Guid
Guid.NewGuid()crypto.randomUUID(), Guid.Empty, Guid.Parse; string wire