Type Casting
Transpose implements C# casting and type-testing over the runtime's type system. This page describes what is checked at runtime, what is erased, and where the behaviour differs from .NET.
is and as
is, as and pattern matching all work, and are backed by a runtime type check
against the object's registered type:
public void Process(object obj)
{
if (obj is string s)
{
Console.WriteLine("String: " + s);
}
else if (obj is int i)
{
Console.WriteLine("Int: " + i);
}
if (obj is IList<string> list)
{
Console.WriteLine("List count: " + list.Count);
}
}
as yields null when the test fails, exactly as in .NET, and is with a pattern
handles the type test and the binding together.
Explicit casts
An explicit cast is emitted as a checked cast — it verifies the runtime type
and throws InvalidCastException on failure, matching .NET:
object o = "hello";
int i = (int)o; // throws InvalidCastException
This applies to explicit reference conversions (a downcast like (Dog)someAnimal,
an interface cast like (IFoo)obj) and to unboxing conversions ((int)someObject).
Casts that are erased
Not every cast needs a runtime check, and Transpose emits nothing for the ones that cannot fail or cannot be verified. A cast is erased when the target is:
- An upcast, an identity conversion, or a boxing conversion — it cannot fail.
object— everything is anobject.- A numeric or enum conversion — these are value conversions, not type tests; they are emitted as the appropriate conversion instead (see below).
- A type parameter (
T) ordynamic— there is no concrete type to check. - An array type — a JavaScript array carries no element-type metadata, so
(Emoji[])Enum.GetValues(…)cannot be verified. - A delegate type — a delegate is a plain JavaScript function with no constructible runtime type token.
- A tuple type.
- An external (native-JavaScript) type from a binding library — a DOM node or a third-party object has no Transpose type identity to test.
- A user-defined conversion operator — the operator is called instead.
null.
The practical consequence: a cast to a DOM or binding-library type always succeeds,
so a wrong one surfaces later as an undefined member rather than as an
InvalidCastException at the cast. Casts to your own types and to System.* types
are checked.
Numeric conversions
An explicit numeric cast is emitted as the conversion C# requires, not as a type test:
- To a ≤32-bit integer or
char— the value is truncated and wrapped to that bit width, so(byte)300gives44as it does on .NET. - To
long/ulong— the value is wrapped into the runtime's 64-bit integer type. - From
long/ulongto a floating type or an enum — the numeric magnitude is read out. - To
decimal— the value is wrapped into the runtime's decimal type. - From
decimalto a floating type — the value is read out as anumber. char↔int— erased; acharis its code unit.
Because long and decimal are runtime objects rather than JavaScript numbers,
these conversions allocate. See
Working with Data.
Casting across the interop boundary
To reinterpret a value whose type only the JavaScript side knows — a payload from an
external API, or a value you are treating structurally — a double cast through
object is the idiomatic form, and it erases to nothing:
var options = (MyOptions)(object)someExternalValue;
That works because MyOptions here would typically be an
[ObjectLiteral] type — a plain
JavaScript object with no runtime identity to check.
For an [External] type the cast is erased outright, so a helper needs no
attribute:
[External]
public class JsValue
{
public extern T As<T>(); // erased: a cast to T is never checked
}
Attributes
[IgnoreCast]
In h5, [IgnoreCast] removed the runtime check for casts to the annotated type.
Transpose already erases casts to every external (native-JavaScript) type, which
was that attribute's dominant use, so [IgnoreCast] is accepted but has no effect —
the behaviour it asked for is the default.
[Cast]
h5's [Cast] supplied a custom JavaScript template for a cast. Transpose does
not implement it. Use an ordinary method with a
[Template]
instead, which gives you the same control with an explicit call site:
[External]
public static class Convert
{
[Template("parseInt({0}, 10)")]
public static extern int ToInt(object value);
}
There is no global switch for cast checking
h5 had an ignoreCast option in h5.json. Transpose has no equivalent setting: the
erasure rules above are fixed. If a checked cast is measurably costing you in a hot
path, restructure to a pattern test (is) whose result you reuse, rather than
looking for a flag.
See also
- How It Works — the type system the checks run against, and the struct value-copy caveat.
- Attribute Reference — the status of every code-generation attribute.