Skip to main content

Enum Marshal

Normative: Default Marshal rules for C# enum between Lua and C#. Related: Type-table constant fields → ../02-TYPE-SYSTEM.md §Enum; boxed form → ../05-LIB.md box/unbox; ref enum → 03-BYREF.md; [LuaMarshalAs]02-MARSHAL-AS.md.

Platform principle: Mono and Il2Cpp share the same Lua-visible semantics; enums default to integer / number Marshal and do not push userdata.

1. Design points

Enums are value types in C# with a single underlying integer field. On the Lua side:

ScenarioShape
Default argumentinteger (preferred on Lua 5.4+) or number
Boxed instanceByObjUserData; only via explicit zlua.box
Type-table constantsinteger / number fields (not userdata)

Enum type tables have no SMT.__call; you cannot construct ByVal userdata via EnumType(...) as with structs.

2. Default rules (C# ↔ Lua)

Without [LuaMarshalAs]:

DirectionDefault shapeNotes
C# → Luainteger (preferred) or numberPush enum underlying integer; do not push userdata
Lua → C#integer / numberAccept integral Lua values; convert by target enum underlying type via Enum.ToObject / equivalent
Lua → C# (alternate)ByObjUserData (boxed enum)Unbox underlying integer from boxed object

Not accepted (unless [LuaMarshalAs] says otherwise): default Marshal as string (enum name), boolean, or ordinary table.

3. Underlying type and range

Codegen / reflection must read the enum underlying type (System.Int32, System.Byte, etc.):

Underlying typePush preferencePop accepts
sbyteulonginteger / numberinteger / number (must be integral)
Non-integral underlying (rare)numbernumber

On Pop, validate the Lua integral value fits the underlying type range; out of range → luaL_error.

Integer primitive rules (integer vs number): 01-OVERVIEW.md §3.

4. Relation to type-table constant fields

At bind time, enum public static literals are written into type table E:

local Color = CSharp.AC['MyGame.Color']
assert(Color.Red == 0) -- integer / number, not userdata

Access path: CSharp.AC['MyGame.Color'].Red.

These are equivalent as enum parameters under default marshal:

local e = Color.Red
foo(e)
foo(Color.Red)
foo(1) -- bare integer; must convert to that enum

Details: ../02-TYPE-SYSTEM.md §Enum types.

5. Boxed form (non-default, zlua.box)

When a script needs a boxed enum instance (object parameter, Array.SetValue, long-lived ByObj, etc.):

local Color = CSharp.AC['MyGame.Color']
local boxed = zlua.box(Color, Color.Red)
-- or
local boxed2 = zlua.box(Color, 2)
ItemNotes
First argEnum type table, zlua.typeof(E), or equivalent typeArg
Second arginteger / number (integral); or same-enum constant field value
ReturnByObjUserData (boxed object; not struct ByVal payload)
Unboxzlua.unbox(boxed) → underlying integer

As an enum parameter into C#, both ByObjUserData and integer/number are accepted (§2).

zlua.box product for ref Color: ByObjUserData follows the 03-BYREF.md reference/temp-slot path (not ByValUserData payload pass-through).

6. ref / out / in enum parameters

See 03-BYREF.md:

Lua argumentBehavior
OpaqueValue (type-compatible)Pass handle address
ByValUserData (if present and type == enum)Pass payload address
integer / numberCopy into stack temp, pass temp address; bare Lua value unchanged
zlua.box product (ByObj)Write pointer into temp slot, pass temp address

C#→Lua: ref enum defaults to OpaqueValue; see 04-OPAQUE.md.

7. [LuaMarshalAs] extensions

Annotationenum by-val
Default§2 rules
UserDataIllegal (by-val enum cannot force userdata); fall back to Default; Editor error log
OpaqueValueby-val legal (C#→Lua; usually unnecessary); for ref/out/in, C#→Lua already defaults to OpaqueValue
Table / UnpackedValuesIllegal (only struct / class / interface)

Boxed form still requires zlua.box; no enum SMT.__call.

Illegal annotation behavior: 02-MARSHAL-AS.md §Illegal annotations.

8. Differences vs struct / class (summary)

Itemenumstructclass
Default across boundaryinteger/numberByValUserData / StructUserDataClassUserData
Type table __callNo.ctor.ctor
boxed / instance constructionOnly zlua.boxType(...) / _defaultType(...)
Type-table constantsinteger/numberUsually noneStatic members
ref write-backOpaque / matching ByValUserDataByValUserData / Opaque; others → tempSee 06-CLASS.md

9. Mono / Il2Cpp consistency

ItemRequirement
Default Push / Popinteger/number ↔ underlying integer
Type-table constantsinteger/number
boxedzlua.box → ByObjUserData
Range checksConsistent
Error messagesSame or equivalent
DocContents
01-OVERVIEW.mdDefault matrix, integer/number
03-BYREF.mdref / out / in
04-OPAQUE.mdC#→Lua byref
05-STRUCT.mdByVal / ByObj vs box
../02-TYPE-SYSTEM.mdEnum type-table structure
../05-LIB.mdbox, unbox