Before we dig deeper in MoonSharp and CLR integration, we need to clarify how types are mapped back and forth. Sadly, back is very different from forth and so we will analyze the two separately.
Isn’t this a bit too complex? - you might ask.
Sure it is. Automatic things are nice and good but when they fail, they fail in terribly complex ways. When in doubt or the thing gets too complicated, you need to simplify things.
There are two ways: just use
DynValue
instead or use custom converters.Not only you would gain in sanity and simplicity, both these solution are also sensibly faster than auto-conversion!
It’s possible to customize the conversion process, however the setting is global and affects all scripts.
To customize conversions, simply set the appropriate callbacks to Script.GlobalOptions.CustomConverters
.
For example, if we want all StringBuilder
objects to be converted to uppercase strings when conversion from CLR to script happens, do:
If we want to customize how all tables are converted when they should match a IList<int>
we can write
If a converter returns null
, the system will behave as if no custom converter existed and attempt an auto-conversion.
This conversion is applied in the following situations:
DynValue.FromObject
System.Object
in place of a DynValue
This conversion is actually quite simple. The following table explains the conversions:
CLR type | C# friendly name | Lua type | Notes |
---|---|---|---|
void | (no value) | This can be applied to return values of methods only. | |
null | nil | Any null will be converted to nil. | |
MoonSharp.Interpreter.DynValue | * | The DynValue is passed through. | |
System.SByte | sbyte | number | |
System.Byte | byte | number | |
System.Int16 | short | number | |
System.UInt16 | ushort | number | |
System.Int32 | int | number | |
System.UInt32 | uint | number | |
System.Int64 | long | number | The conversion can lead to a silent precision loss. |
System.UInt64 | ulong | number | The conversion can lead to a silent precision loss. |
System.Single | float | number | |
System.Decimal | decimal | number | The conversion can lead to a silent precision loss. |
System.Double | double | number | |
System.Boolean | bool | boolean | |
System.String | string | string | |
System.Text.StringBuilder | string | ||
System.Char | char | string | |
MoonSharp.Interpreter.Table | table | ||
MoonSharp.Interpreter.CallbackFunction | function | ||
System.Delegate | function | ||
System.Object | object | userdata | Only if the type has been registered for userdata. |
System.Type | userdata | Only if the type has been registered for userdata, static members access. | |
MoonSharp.Interpreter.Closure | function | ||
System.Reflection.MethodInfo | function | ||
System.Collections.IList | table | The resulting table will be indexed 1-based. All values are converted using these rules. | |
System.Collections.IDictionary | table | All keys and values are converted using these rules. | |
System.Collections.IEnumerable | iterator | All values are converted using these rules. | |
System.Collections.IEnumerator | iterator | All values are converted using these rules. |
This includes a pretty good coverage of collections, as most collections do implement IList, IDictionary, IEnumerable and/or IEnumerator. In case you are writing your own collections, remember to implement one of these non-generic interfaces.
Every value which cannot be converted using this logic will throw a ScriptRuntimeException.
The opposite conversion is quite more complex. In fact, there exist two different conversion paths - the “standard” one, and the “constrained” one. The first applies everytime you ask to convert a DynValue
to an object without specifying what actually you want to receive, the other when there is a target System.Type
to match.
This is used:
DynValue.ToObject
Here we see the default conversion. It’s actually simple:
MoonSharp type | CLR type | Notes |
---|---|---|
nil | null | Applied to every value which is nil. |
boolean | System.Boolean | |
number | System.Double | |
string | System.String | |
function | MoonSharp.Interpreter.Closure | If DataType is Function. |
function | MoonSharp.Interpreter.CallbackFunction | If DataType is ClrFunction. |
table | MoonSharp.Interpreter.Table | |
tuple | MoonSharp.Interpreter.DynValue[] | |
userdata | (special) | Returns the object stored in userdata. If a "static" userdata, the Type is returned. |
Every value which cannot be converted using this logic will throw a ScriptRuntimeException.
The constrained auto-conversion is a lot more complex though. In this case a MoonSharp value must be stored in a CLR variable of a given Type and there are pretty much infinite ways to do these conversions.
This is used:
DynValue.ToObject<T>
MoonSharp attempts very hard to convert values, but the conversion surely shows some limits, specially when tables are involved.
In this case, the conversion is more a process than a simple table of mapping, so let’s analyze the target types one by one.
MoonSharp.Interpreter.DynValue
it is not converted and the original value is returned.System.Object
, the default conversion, detailed before, is applied.nil
value to be mapped, null
is mapped to reference types and nullable value types and an attempt to match a default value is used in some cases (for example function calls for which a default is specified) for non-nullable value types, otherwise an exception is thrown.Strings can be automatically converted to System.String
, System.Text.StringBuilder
or System.Char
.
Booleans can be automatically converted to System.Boolean
and/or System.Nullable<System.Boolean>
. They can also be converted to System.String
, System.Text.StringBuilder
or System.Char
.
Numbers can be automatically converted over System.SByte
, System.Byte
, System.Int16
, System.UInt16
, System.Int32
, System.UInt32
, System.Int64
, System.UInt64
, System.Single
, System.Decimal
, System.Double
and their nullable counterparts. They can also be converted to System.String
, System.Text.StringBuilder
or System.Char
.
Script functions are converted to MoonSharp.Interpreter.Closure
or MoonSharp.Interpreter.ScriptFunctionDelegate
.
Callback functions are converted to MoonSharp.Interpreter.ClrFunction
or System.Func<ScriptExecutionContext, CallbackArguments, DynValue>
.
Userdatas are converted only if they are not “static” (see the userdata section in the tutorials). They are converted if the desired type can be assigned with the object to be converted.
They can also be converted to System.String
, System.Text.StringBuilder
or System.Char
, by calling the object ToString() method.
Tables can be converted to:
MoonSharp.Interpreter.Table
- of courseDictionary<DynValue, DynValue>
.Dictionary<object, object>
. Keys and values are mapped using the default mapping.List<DynValue>
.List<object>
. Elements are mapped using the default mapping.DynValue[]
.object[]
. Elements are mapped using the default mapping.T[]
, IList<T>
, List<T>
, ICollection<T>
, IEnumerable<T>
, where T is a convertible type (including other lists, etc.)IDictionary<K,V>
, Dictionary<K,V>
, where K and V are a convertible type (including other lists, etc.)Note that conversion to generics and typed arrays have the following limitations:
Some conversions (for example to
List<int>
) might cause problems on AOT platforms like Unity/iOS. Register a custom converter to workaround issues of this kind.If targeting AOT platforms like iOS, always be careful with generics of value types. Generics of reference types do not, in general, create problems.