How It Works
Transpose is a source-to-source compiler: it reads C# and emits JavaScript. You write application logic against a browser-side implementation of the .NET base class library, and the result runs in any browser.
Compilation pipeline
Transpose is built entirely on Roslyn — the same compiler front end the C# compiler uses. There is no separate parser and no intermediate lowering pass.
Bind:
Roslyn parses the sources and builds a compilation whose only base-library
reference is Transpose.dll, the browser-side BCL. Every type your code uses
resolves against that.
Report Roslyn diagnostics:
Ordinary C# errors fail the build first, in the same form the C# compiler
produces them. A misspelled member is CS0117, not a runtime surprise.
Scan for unsupported features:
Constructs that have no browser equivalent — DllImport / LibraryImport
P/Invoke, inline arrays — are reported as errors rather than emitted as broken
code.
Emit:
The emitter walks the Roslyn syntax tree, guided by the semantic model, and writes JavaScript directly.
Write the output:
The runtime, the bundle, the reflection metadata, resources and index.html are
assembled into the output folder — or, for a library, embedded into a .NET DLL
that referencing projects consume.
The output is reproducible: the same sources and the same compiler produce a byte-identical site and a byte-identical assembly.
Runtime environment
Generated code depends on a small runtime library, tps.js, which ships inside
the Transpose.BCL package. It provides:
- Type system — C# classes, structs, interfaces, enums and generics
modelled in JavaScript. Types are registered with
Transpose.define(...). - Base class library — implementations of the core .NET types:
System.String, the integer and floating-point types,System.Decimal,System.Int64,System.DateTime,System.TimeSpan,System.Console,System.Text.RegularExpressions, the generic collections, LINQ,Task, and more. - Language helpers — reached through the
TransposeRshim. - Reflection — the metadata that backs
System.Reflectionat runtime.
The generated index.html loads tps.js before your bundle. A project that
supplies its own page has to do the same, in that order.
C# feature support
Transpose supports the C# language broadly, including:
- Classes, structs and records — inheritance, polymorphism,
withexpressions. - Interfaces — including default interface members.
- Generics — including constraints; type arguments are threaded at runtime where the emitted code needs them.
- Lambdas, local functions and delegates — emitted as JavaScript functions.
- LINQ — see LINQ Support.
async/await— see Async Support.- Iterators —
yield return/yield break. - Pattern matching — type, constant, relational, list and property patterns,
switchexpressions. - Events, operator overloading, indexers, extension methods, nullable reference types, tuples and deconstruction.
- Attributes — both to drive code generation and as reflection metadata. See the Attribute Reference.
Differences from .NET on the desktop
Some differences follow from the browser, and some are current limitations of the compiler. Both matter when porting code.
Consequences of running in a browser
- Garbage collection is the JavaScript engine's. There is no
GCto tune and no finalizer guarantee. - Threading: JavaScript is single-threaded.
Taskandasync/awaitwork through the event loop, butSystem.Threading.Threaddoes not, andTask.Rundoes not move work to another thread — see Async Support. - File system:
System.IOfile access has no browser equivalent. UselocalStorage, IndexedDB, or the File System Access API throughTranspose.Core. - P/Invoke (
DllImport/LibraryImport) and inline arrays are reported as compile errors.
Current compiler limitations
- Struct value-copy semantics apply to structs declared in the project being
compiled. Assigning such a struct, passing it by value, returning it, boxing
it, or putting it in a collection clones the value, recursively through its
struct-typed fields. A struct from a referenced library, a BCL struct
(
DateTime,TimeSpan, …) and aValueTupleare copied by reference instead, sovar b = a; b.Inner.V = 9;writes through toafor those. This is a deliberate trade-off: widening it would make everyDateTimeor tuple assignment allocate. Span<T>does not accept the implicit array conversion.Span<int> s = new int[3];emits the bare array, sos[0] = 1fails at runtime. Use the array directly.- A positional pattern only resolves members for tuples and records.
x is Foo(1, 2)against a type with a hand-writtenDeconstruct(out …)readsItem1/Item2instead of callingDeconstruct, because a pattern test is emitted as a single expression and cannot pass out-parameters. - Source maps are not emitted yet — see Exceptions & Debugging.
- Module formats (AMD / CommonJS / ES6 / UMD) are not implemented — see Output Types.