Unsupported Features

This page lists the C# features and .NET APIs that Transpose does not compile, the error it reports for each, and what to use instead. It also lists the constructs that compile but behave differently from .NET, which the compiler does not report.

For what is supported, see C# Language Support.

How the compiler reports them

Every unsupported construct is a build error, never a warning: the build fails and no JavaScript is written. The errors come from four places:

Source Code What it reports
The unsupported-feature scan TransposeR0001 Constructs and APIs with no browser equivalent. Reports every occurrence in the project.
The duplicate-name check TransposeR0003 Two members that would get the same JavaScript name. Runs with the scan.
Roslyn, compiling against the browser BCL CS…. Types, members and runtime features that Transpose.dll does not provide.
The emitter TransposeR0001 ("…not supported yet: …") Constructs the JavaScript emitter cannot translate. Stops at the first occurrence.

The scan and Roslyn's diagnostics are reported together, in one list ordered by file and line. The emitter only runs when that list is empty.

tps writes each error in MSBuild's canonical format, with an absolute path, line and column, so it appears in the IDE's error list and navigates to the source:

/src/App/Program.cs(11,24): error TransposeR0001: File I/O (System.IO.File) is not supported in the browser environment.
/src/App/Program.cs(12,13): error TransposeR0001: Threading primitives (System.Threading.Thread) are not supported in the browser environment.
/src/App/Program.cs(13,25): error TransposeR0001: checked arithmetic (overflow checking) is not supported in the browser environment.

3 error(s), by id: TransposeR0001×3

These errors cannot be suppressed: #pragma warning disable TransposeR0001 has no effect. For code shared with a desktop .NET project, exclude the unsupported part with the TRANSPOSE symbol, which the SDK always defines:

#if TRANSPOSE
    var settings = LoadFromLocalStorage();
#else
    var settings = File.ReadAllText("settings.json");
#endif
The emitter reports one error per build

The scan lists everything it finds, but an error from the emitter stops the build at that point. A project with three goto case statements shows one error per build until all three are fixed.

Summary

Construct Error
Pointers, &, * TransposeR0001 Pointers are not supported in the browser environment.
unsafe blocks and members, fixed TransposeR0001 Unsafe code is not supported in the browser environment.
stackalloc into a pointer TransposeR0001 stackalloc into a pointer is not supported in the browser environment; allocate into a Span instead.
sizeof TransposeR0001 Translation of this construct is not supported yet: SizeOfExpression
[DllImport], [LibraryImport] TransposeR0001 Native interop (P/Invoke) is not supported in the browser environment.
extern method with no JavaScript mapping TransposeR0001 Native interop (extern methods) is not supported in the browser environment.
checked integer arithmetic TransposeR0001 checked arithmetic (overflow checking) is not supported in the browser environment.
nint / nuint TransposeR0001 Native-sized integers (nint/nuint) are not supported in the browser environment.
enum E : long / : ulong TransposeR0001 An enum with a 64-bit underlying type ('long') is not supported…
[InlineArray] structs TransposeR0001 Inline arrays are not supported in the browser environment.
ref / out / in lambda parameters TransposeR0001 ref/out/in parameters on lambdas are not supported in the browser environment.
Top-level statements TransposeR0001 Top-level statements are not supported; use an explicit class with a Main method.
global using TransposeR0001 Global usings are not supported; add per-file using directives instead.
goto case, goto default TransposeR0001 Translation of this construct is not supported yet: goto
C# 14 extension blocks TransposeR0001 Translation of this construct is not supported yet: Extension members (C# 14 extension blocks) are not supported
Default interface members CS8701 Target runtime doesn't support default interface implementation.
Static abstract / static virtual interface members CS8919 Target runtime doesn't support static abstract members in interfaces.
File system: File, Path, Directory, FileStream, BinaryWriter, … TransposeR0001 File I/O (System.IO.…) is not supported in the browser environment.
Sockets: System.Net.Sockets.* TransposeR0001 Sockets (System.Net.Sockets…) are not supported in the browser environment.
Threads and locks: Thread, Monitor, Interlocked, Lock, SemaphoreSlim, … TransposeR0001 Threading primitives (System.Threading.…) are not supported in the browser environment.
Two members with the same JavaScript name TransposeR0003 Two members of 'C' are emitted as 'run', which JavaScript cannot represent…
APIs missing from the browser BCL CS0246, CS1061, CS0117, …

Language features

Unsafe code and pointers

JavaScript has no addressable memory, so pointer types, the address-of (&) and indirection (*) operators, unsafe blocks and members, fixed statements and fixed-size buffers, stackalloc into a pointer and sizeof are all rejected. stackalloc into a Span<T> is supported.

public static unsafe void Main()
{
    int x = 10;
    int* p = &x;               // TransposeR0001: Pointers are not supported
    Console.WriteLine(*p);     // TransposeR0001: Pointers are not supported
}

int* buffer = stackalloc int[4];        // TransposeR0001: stackalloc into a pointer is not supported
Span<int> span = stackalloc int[4];     // compiles
Console.WriteLine(sizeof(int));         // TransposeR0001: …not supported yet: SizeOfExpression

Use arrays instead. For binary data, Transpose.Core binds the JavaScript typed arrays (Uint8Array, Float32Array, …).

Native interop

P/Invoke has no browser equivalent. A method with [DllImport] or [LibraryImport] is rejected, and so is any extern method that Transpose cannot map to JavaScript.

[DllImport("user32.dll")]      // TransposeR0001: Native interop (P/Invoke) is not supported
static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);

An extern method is supported when it declares a JavaScript binding: when the method carries [Template], [Name], [External] or [Script], when its type is [External], [ObjectLiteral] or [GlobalMethods], or when its assembly is [assembly: External]. That is how binding libraries such as Transpose.Core declare the browser APIs. See Calling JavaScript.

[External, Name("console")]
public static class JsConsole
{
    [Name("log")]
    public static extern void Log(string message);   // compiles: calls console.log
}

Checked arithmetic

JavaScript arithmetic does not report overflow, so an OverflowException cannot be raised. checked blocks and checked(...) expressions are rejected when they contain built-in +, - or * on an integer type.

int max = int.MaxValue;
int total = checked(max + 1);   // TransposeR0001: checked arithmetic (overflow checking) is not supported

Integer arithmetic outside checked wraps as it does in unchecked C#: int.MaxValue + 1 is -2147483648. unchecked is accepted. checked around user-defined operators is accepted too, since it calls the operator's checked overload and needs no overflow detection. To detect overflow, compare against the limits before the operation:

if (a > int.MaxValue - b) throw new OverflowException();
int total = a + b;

Native-sized integers

nint and nuint have no JavaScript representation distinct from a number.

nint n = 5;   // TransposeR0001: Native-sized integers (nint/nuint) are not supported

Use int or long.

Enums with a 64-bit underlying type

Enum members compile to plain JavaScript numbers, which hold integers exactly only up to 2^53. An enum whose underlying type is long or ulong is rejected, since members above that limit would get the wrong value or collide.

public enum Big : long { A = 1, B = 5_000_000_000 }
// TransposeR0001: An enum with a 64-bit underlying type ('long') is not supported in the
// browser environment; enum members are JavaScript numbers, which represent integers
// exactly only up to 2^53.

int, uint and every narrower underlying type work.

Inline arrays

[InlineArray(4)]
public struct Buffer4 { private int _element; }   // TransposeR0001: Inline arrays are not supported

Use an array field.

ref, out and in lambda parameters

A lambda compiles to a JavaScript closure, which cannot take a parameter by reference. This covers both the explicitly typed form and the C# 14 short form.

public delegate void RefAction(ref int x);

RefAction a = (ref int x) => x++;   // TransposeR0001: ref/out/in parameters on lambdas are not supported
RefAction b = (ref x) => x++;       // same error

Use a method instead: a delegate with ref parameters works when its target is a method. Or return the new value from the lambda.

static void Increment(ref int x) => x++;

RefAction a = Increment;            // compiles and writes back through the ref
Func<int, int> next = x => x + 1;   // or return the new value

Top-level statements

A program needs an explicit entry point.

// Program.cs
Console.WriteLine("hi");   // TransposeR0001: Top-level statements are not supported;
                           // use an explicit class with a Main method.
public static class Program
{
    public static void Main() => Console.WriteLine("hi");
}

Main can also be async Task Main().

Global using directives

global using System;   // TransposeR0001: Global usings are not supported; add per-file using directives instead.

Add the using directives to each file.

goto case and goto default

goto to a label is supported. Jumping between switch sections is not.

switch (state)
{
    case 3:
        Console.WriteLine("three");
        goto case 4;   // TransposeR0001: Translation of this construct is not supported yet: goto
    case 4:
        Console.WriteLine("four");
        break;
}

Move the shared code into a method, or put a label after the switch and use goto to that label.

Extension blocks

The C# 14 extension block is rejected. Classic extension methods work.

public static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source)   // TransposeR0001: Extension members (C# 14 extension blocks) are not supported
    {
        public bool IsEmpty => !source.Any();
    }
}
public static class EnumerableExtensions
{
    public static bool IsEmpty<T>(this IEnumerable<T> source) => !source.Any();
}

An extension property has no classic equivalent, so it becomes a method.

Default and static abstract interface members

Transpose.dll, the browser BCL, does not declare the runtime features for interface members with bodies or for static abstract members, so Roslyn rejects them before Transpose's own checks run.

public interface IGreeter
{
    string Name { get; }
    string Greet() => "Hello, " + Name;   // CS8701: Target runtime doesn't support default interface implementation.
}

public interface IZero<T> where T : IZero<T>
{
    static abstract T Zero { get; }       // CS8919: Target runtime doesn't support static abstract members in interfaces.
}

Move a default implementation into an abstract base class or an extension method. Generic math (INumber<T> and the rest of System.Numerics) depends on static abstract members and is not available.

.NET APIs with no browser equivalent

File system, socket and thread APIs are reported as TransposeR0001, naming the type. This covers every type in System.IO, System.Net.Sockets and System.Threading, apart from the allowed types listed below:

  • A type the browser BCL declares, such as File, Thread or Interlocked, is reported wherever it is used: a declaration, a constructor call, a static member access, or a var whose inferred type is one of them. Aliases (using F = System.IO.File;) and using static imports are followed to the type they name. Types that a referenced library declares in those namespaces are covered too.
  • A type the browser BCL does not declare, such as Path, SemaphoreSlim or anything in System.Net.Sockets, would otherwise be a Roslyn "type not found" error. It is reported with the same message when the name is written with its namespace (System.IO.Path), or when it is a .NET type from one of those namespaces and the file imports that namespace with using. Any other unknown name, such as a misspelling, stays an ordinary CS0246/CS0103 error.

Importing a namespace with using is not an error on its own, except for using System.Net.Sockets;, a namespace the browser BCL does not have at all.

File system

System.IO is rejected, apart from the in-memory types the runtime implements:

Allowed Rejected (examples)
Stream, MemoryStream, BufferedStream File, FileStream, FileInfo
StringReader, StringWriter BinaryReader, BinaryWriter
StreamReader, StreamWriter over a stream Path, Directory, DirectoryInfo, FileSystemWatcher
TextReader, TextWriter, SeekOrigin everything in System.IO.Compression
IOException, EndOfStreamException
var text = File.ReadAllText("settings.json");   // TransposeR0001: File I/O (System.IO.File) is not supported

var writer = new StringWriter();                // compiles
writer.Write("hello");

In the browser, use localStorage, IndexedDB or the File System Access API through Transpose.Core, or fetch the file over HTTP with Transpose.HttpClient.

Sockets

Every type in System.Net.Sockets is rejected, and so is using System.Net.Sockets;. Use WebSocket from Transpose.Core, or HTTP.

Threads and locks

JavaScript runs your code on a single thread, so the thread and synchronisation types in System.Threading are rejected: Thread, ThreadPool, Monitor, Interlocked, Lock, Mutex, SemaphoreSlim, ManualResetEvent and the other wait handles, Timer and the rest.

var t = new Thread(() => Work());   // TransposeR0001: Threading primitives (System.Threading.Thread) are not supported
Thread.Sleep(100);                  // TransposeR0001: same error
Interlocked.Increment(ref count);   // TransposeR0001: Threading primitives (System.Threading.Interlocked) are not supported

static readonly Lock Gate = new();  // TransposeR0001: Threading primitives (System.Threading.Lock) are not supported
var gate = new SemaphoreSlim(1);    // TransposeR0001: Threading primitives (System.Threading.SemaphoreSlim) are not supported

The task-based async model is allowed, since it runs on the event loop:

  • all of System.Threading.Tasks: Task, Task<T>, ValueTask, TaskCompletionSource, TaskCanceledException, …
  • CancellationToken, CancellationTokenSource, CancellationTokenRegistration and Timeout.

A lock statement on an ordinary object compiles to its body, because there is nothing to lock against:

static readonly object Gate = new();

lock (Gate)          // compiles; runs the body
{
    count++;
}

Replace Thread.Sleep(ms) with await Task.Delay(ms), and a background thread with an async method. See Async Support.

Duplicate JavaScript names

A type's methods become the keys of one JavaScript object, so two methods that end up with the same JavaScript name cannot both exist. C# overloads are numbered and never collide. Two members given the same [Name] do.

public class Commands
{
    [Name("run")] public void Start() { }
    [Name("run")] public void Start(int delay) { }
}
// TransposeR0003: Two members of 'Commands' are emitted as 'run', which JavaScript cannot
// represent - only the last would exist at runtime. Give one of them a different [Name],
// or remove the overload: Start(), Start(int)

The error is reported on every member involved. A static and an instance member may share a name, since they are emitted into different objects.

APIs missing from the browser BCL

Your code compiles against Transpose.dll, a browser implementation of the .NET base class library, not the real one. A type or member it does not implement is an ordinary Roslyn error, the same one a misspelled name would produce. Examples:

Code Error
IAsyncEnumerable<T>, await foreach CS0246 The type or namespace name 'IAsyncEnumerable<>' could not be found
IAsyncDisposable, await using CS0246 The type or namespace name 'IAsyncDisposable' could not be found
System.Numerics.INumber<T> CS0234 The type or namespace name 'Numerics' does not exist in the namespace 'System'
span.Reverse(), span.Sort() (MemoryExtensions) CS1061 'Span' does not contain a definition for 'Sort'
expression.Compile() on an Expression<TDelegate> CS1061 'Expression<Func<int, int>>' does not contain a definition for 'Compile'

The NuGet packages add more of the .NET surface: Transpose.Newtonsoft.Json, Transpose.System.Text.Json and Transpose.HttpClient. For anything else, bind the JavaScript API you need with [External]; see Calling JavaScript.

Compiles but behaves differently

The constructs below compile without an error and produce different results from .NET. The compiler does not report them, so check for them when porting code.

Construct Behaviour in the browser
A boxed number (object o = 1;) Loses its exact type: o is int and o is double are both true. long, ulong and decimal are unaffected.
dynamic passed to an overloaded method (Enumerable.Sum(d)) No runtime overload resolution. The call throws TypeError: … is not a function.
Assigning a struct from a referenced library, a BCL struct (DateTime) or a ValueTuple Copies the reference, not the value. Structs declared in your project are copied by value.
A long read from a JavaScript binding (for example Blob.size) A JavaScript number, exact only up to 2^53.

A boxed number, for example:

object boxed = 1;
Console.WriteLine(boxed is double);   // .NET: False    Transpose: True

Test for the value's range or use a typed variable instead of testing a boxed number's type.

© 2026 Curiosity. All rights reserved.