Dictionary/Arrays could work but if the function is recursive or calls other functions which rely on the same Dictionary/Array you could accidentally overwrite existing return values (unless you create dict/array each time).
I've used the string splitting option before for returning values.
Another approach that I use is creating specific Return Objects. So for example, if I have a function that needs to return a IsStunned boolean, a Damage number variable, and a string TypeOfAttack, then I would create a Sprite object called RetAttack which would have IsStunned, TypeOfAttack, and Damage as instance variables.
The function would then spawn a new instance of this object, assign the variable values, and the function's return value would be the UID of the RetAttack object.
I like this approach since it provides strongly-typed returns that can be complex objects, and there is no unnecessary parsing/type conversions either (as would be the case if you joined all three in a single string).
After returning, you would pick RetAttack by the ret UID and extract the return values (TypeOfAttack, IsStunned, Damage) then destroy the RetAttack object.