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 |
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,ThreadorInterlocked, is reported wherever it is used: a declaration, a constructor call, a static member access, or avarwhose inferred type is one of them. Aliases (using F = System.IO.File;) andusing staticimports 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,SemaphoreSlimor anything inSystem.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 withusing. Any other unknown name, such as a misspelling, stays an ordinaryCS0246/CS0103error.
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,CancellationTokenRegistrationandTimeout.
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 |
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.
Related
- C# Language Support
- How It Works — the compilation pipeline and where the scan runs
- Exceptions & Debugging
- CLI Reference —
--max-errorsand the othertpsoptions