Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice
  2. Ever participated in one our Game Jams? Want pointers on your project? Our Evangelists will be available on Friday to give feedback. Come share your games with us!
    Dismiss Notice

Get variable from function, get its type, switch through type, cast as type

Discussion in 'Scripting' started by asidjasidjiasd, Feb 21, 2020.

  1. asidjasidjiasd

    asidjasidjiasd

    Joined:
    Feb 9, 2020
    Posts:
    12
    Code (CSharp):
    1.  
    2.  
    3.             object myObject = getMyObject(someVar);
    4.          
    5.  
    6.             Type type = myObject.GetType();
    7.  
    8.  
    9.             switch (type)
    10.             {
    11.                 int:
    12.                     int myInt = (type)myObject;
    13.                 break;
    14.  
    15.                 string:
    16.                     string myString = (type)myObject;
    17.                 break;
    18.  
    19.                 MyClass:
    20.                     MyClass myClass = (type)myObject;
    21.                 break;
    22.             }
    The code above doesn't work for obvious reasons. I would like to get an object from a function. Get that objects type and execute specific code for every variant of that object. Integers will be handled differently than string or MyClass. Is there a way to get it done?
     
    Last edited: Feb 21, 2020
  2. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,021
    Code (CSharp):
    1.             switch (myObject)
    2.             {
    3.                 case int myInt:
    4.                     Debug.Log(myInt);
    5.                 break;
    6.                 case string myString:
    7.                     Debug.Log(myString);
    8.                 break;
    9.             }
     
  3. Team2Studio

    Team2Studio

    Joined:
    Sep 23, 2015
    Posts:
    98
    this should do the trick:

    Code (CSharp):
    1. Object myObject = GetMyObject(someVar);
    2.  
    3.             switch(System.Type.GetTypeCode(myObject.GetType()))
    4.             {
    5.                 case System.TypeCode.Int32:
    6.  
    7.                     break;
    8.                 case System.TypeCode.String:
    9.  
    10.                     break;
    11.             }
     
  4. asidjasidjiasd

    asidjasidjiasd

    Joined:
    Feb 9, 2020
    Posts:
    12
    My bad. Didn't explain it properly.
    myObject
    is an object which holds an item that originally was either int, string or MyClass, and I would like to recast
    myObject
    into a new variable of the same type that it is. Another part of it is, I would like to create a switch statement whose execution is dependent of what type
    myObject
    is (or was during assignment).

    Edit: I wrote this before Team2Studios post showed up.
     
  5. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,444
    I'd probably just prefer ifs in this case instead of a switch:
    Code (csharp):
    1.  
    2. if (myObject is int) myInt = (int)myObject;
    3. if (myObject is string) myString = (string)myObject;
    4. if (myObject is MyClass) myClass = (MyClass)myObject;
    5.  
    I'm not sure where typeof(int) is a compile time constant. If it is, you could do:
    Code (csharp):
    1.  
    2. switch (typeof(myObject))
    3. {
    4.     case typeof(int):
    5.         break;
    6.     case typeof(string):
    7.         break;
    8. }
    9.  
    (But I'm guessing that that probably won't work.)

    You could always consider some inheritance through an interface so you can combine some custom structs and classes:
    Code (csharp):
    1.  
    2. public interface IValue
    3. {
    4.     void Execute();
    5. }
    6.  
    7. public struct ValueInt : IValue
    8. {
    9.     public int value;
    10.  
    11.     public void Execute()
    12.     {
    13.     }
    14.  
    15.     // Add implicit casts to and from int to make your life a little easier
    16. }
    17.  
    18. public class ValueClass : IValue
    19. {
    20.     public void Execute()
    21.     {
    22.     }
    23. }
    24.  
    25. IValue myObject;
    26. myObject.Execute();
    27.  
    Then on top you could add a listener:
    Code (csharp):
    1.  
    2. public interface IValueListener
    3. {
    4.     void ReceiveInt(int value);
    5.     void ReceiveString(string value);
    6. }
    7.  
    Add a listener as parameter in Execute and then in ValueInt:
    Code (csharp):
    1.  
    2. public void Execute(IValueListener listener)
    3. {
    4.     listener.ReceiveInt(value);
    5. }
    6.  
     
    Last edited: Feb 21, 2020
  6. Team2Studio

    Team2Studio

    Joined:
    Sep 23, 2015
    Posts:
    98
    @jvo3dc the switch statement you posted will not work. A switch statement needs a variable to compare and you supplied a type. Also, "case" is a constant-expression, it needs a constant value, which "typeof(int)" is not.

    Edit: @jvo3dc your if statements are also incorrect.
    1. if (myObject is int) myInt = (int)myObject;

      Visual Studio gives a warning, saying "The given expression is never of the provided ('int') type.
     
    Last edited: Feb 21, 2020
  7. asidjasidjiasd

    asidjasidjiasd

    Joined:
    Feb 9, 2020
    Posts:
    12
    Is there a way to store it's native type in a tuple, and have it cast as such:
    (myObject.Item1)myObject.Item2
    ?
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    725
    this is the preferred method of type matching, as of C# 7.1 (I think)

    Code (csharp):
    1. void forkMethod(object myObject) {
    2.   if(myObject is int i) doStuff(i);
    3.   else if(myObject is string s) doStuff(s);
    4.   else if(myObject is MyClass m) doStuff(m);
    5.   else throw new System.NotSupportedException();}
    6.  
    7. void doStuff(int arg) { ... }
    8. void doStuff(string arg) { ... }
    9. void doStuff(MyClass arg) { ... }
    10.  
    there is even a newer one since C# 8, called expression switch, look it up.
    oh a basic switch is also fine and works the same way.

    Code (csharp):
    1. switch(myObject) {
    2.    case int i: /* work with i */ break;
    3.    ....
    4.    default: throw new System.NotSupportedException(); // or InvalidCastException();
    5. }
    yes you could, but you'd have to use reflection for that to work properly. reflection isn't performant* and will only harm the readability of your code. I'd do that only if necessary, if you're doing some clever self-referencing code, a scripting language of sorts, dynamic calls. if not, keep it simple, and do the casting fork as shown above. it's pretty common.

    edit: * it isn't too slow either but looks like this (just a snippet from my code)
    Code (csharp):
    1.     static MethodInfo[] prepFieldReflectionFor(string genericMethodName) {
    2.       int fields = VertexRepository.FragFields.Length;
    3.  
    4.       var result = new MethodInfo[fields];
    5.       var method = typeof(VertexRepository).GetMethod(genericMethodName);
    6.  
    7.       for(int i = 0; i < fields; i++) {
    8.         var fieldType = VertexRepository.FragFields[i].FieldType;
    9.         result[i] = method.MakeGenericMethod(fieldType);
    10.       }
    11.  
    12.       return result;
    13.     }
    14.  
    15.     static public Vertex Read(Face face, int vertexIndex)
    16.       => Read(face.Mesh, new VertexKey(face, Face.ValidateIndex(vertexIndex)), face.Mesh.Verts.Channel);
    17.  
    18.     static public Vertex Read(Mesh mesh, VertexKey vk, VertexChannel channel = VertexChannel.All) {
    19.       var vertex = new Vertex(vk);
    20.       prepGetters();
    21.  
    22.       for(int fragIndex = 0; fragIndex < VertexRepository.FragFields.Length; fragIndex++) {
    23.         if(mesh.Verts.Channel.HasFrag(fragIndex) && channel.HasFrag(fragIndex)) {
    24.           var value = _fieldGetters[fragIndex].Invoke(mesh.Verts, new object[] { vk, (VertexFrag)fragIndex });
    25.           VertexRepository.FragFields[fragIndex].SetValueDirect(__makeref(vertex), value); // okaay <.<
    26.         }
    27.       }
    28.  
    29.       return vertex;
    30.     }
     
    Last edited: Feb 22, 2020
    jvo3dc and asidjasidjiasd like this.
  9. asidjasidjiasd

    asidjasidjiasd

    Joined:
    Feb 9, 2020
    Posts:
    12
    I don't think this answer could get more complete than this. Thank you.
     
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,696
    I'd start all over again...

    The first thing to evaluate is whether casting is the only option available in your scenario, or if there might be a better approach to solve the problem at hand using object oriented techniques which could remove casting entirely.

    - What's the specific use case?
    - What kind of data is it that you're dealing with? (not asking for the type here, but rather for semantics)
    - Is the caller really responsible for processing the data by itself, or is there a chance to encapsulate this part?
     
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    725
    But that really goes beyond what this user asked for. I'm just assuming he knows his way in C#, and there are truly times when this kind of object->specific_type casting fork is not just the only option or the best option, but the most maintenance-friendly, and the quickest to implement. Pattern matching was implemented in the compiler exactly to improve the unboxing behavior when such a need arises.

    Many new C# programmers would jump to a generic solution (I did a decade ago, and after extensively learning about it, I saw too many SO questions about this very problem which generics aren't able to solve, and weren't made for), however the fact that this user didn't, is a hint to me that he knows why he asks for it. Maybe I'm wrong of course.

    But in a nutshell, you can overload methods and you can provide generic processors and whatnot, but if an object comes your direction, and is allowed to be just about anything, there is no other option but to unbox it. Yes, I get you, sometimes it smells. But let's not presuppose that every Unity programmer is an utter noob. Every once in a while... right? :) Let's just hope so.
     
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,696
    There's always room for discussions. This community is not as strict as StackOverflow.
    Pattern matching is a great thing. It's truly easy and quick to use, but some features that seem convenient always have the downside that people get used to them too much and they're surprised by the negative aspects of it later.

    For whatever reason he's going that route, we don't know. That's why I asked in the first place, because I'm just interested. Anyone is free to ignore it. :)

    That's a very strange assumption, at least from a logical point of view (based on what we know). :p
    I'm just being interested in the matter. It's just some sort of challenge to analyse a problem starting at its root, not at the very top. It helps to look at problems from many different perspectives, it helps to understand different ways of thinking and the various approaches that other programmers take.

    Anyways, the questions that arise in this particular case:
    Why don't you know what it is? Why is it allowed to be "just about anything" at all?
    Where does it come from, how does it get its identity in the first place?
    Does the caller, who's about to process it, actually have the responsibility to process it?

    The snippet provide by the OP could also just be anything. It could be part of the interface to some unpredictable and uncontrollable input data that needs to find its way back into the strongly typed land. It's also likely that there's was an unfortunate design decision in the code that he's calling into... we don't know, so let's just ask. :p
     
    asidjasidjiasd likes this.
  13. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    725
    everything you say is true.

    as for the strictness of this forum, I certainly hope so. StackOverflow actually constricts quality discussion and discourages the topics of massive interest. I've learned more from the links and humorous comments that somehow survived the inquisition there, than from the actual answers.

    anyway, maybe I am a little biased. I mean, regarding the casting fork.

    I'm currently writing an expression parser of sorts, which starts to look like a proper scripting language, and I've managed to make it really ridiculously tiny (to my shock, even though this is my sixth time in life I've ever attempted to build a fully-fledged language interpreter/compiler; and my third time in C#, though this is strictly a fast AST parser, nothing more), and I've learned a lot about the typing system in the process throughout the years. IT'S HARD. and kinda easy at the same time. really weird. it's a discipline in itself, though I wouldn't call myself an expert. this is why it's hard, I kinda like the pure aesthetics and pleasurable organic synesthesia, not this wickedly surreal realm of discreet abstraction flanging together with the creeping sound of devil's clocks, so I kinda got stuck somewhere in between, I seem to be drawn to both ends of a continuum. I really like games for that reason. you don't get that working for a bank.

    if that wasn't boring enough here are some random ramblings #1
    it's fun when you pursue the weirdest of ideas, like that time when I made an 8-bit type that could represent 4096 states and work just as fast, and then I've built a dynamic pie chart out of it, just because I wanted a game in which every tile in the world would have a chemical composition, and it absolutely had to fit into just one RGBA texture.

    this time I just wanted to play with some easing curves similar to smoothstep and was pissed that I'd have to wait for the math formulas to get compiled, only to have my in-editor graph instantly refreshed. I intend to release it when I'm done. my other hobby is a node-based mesh builder similar to Houdini that is run by a command-line-like quasi-language that could use an expression engine for some fine control, so it sort of blossoms into a beautiful thing. I actually found a bug with Unity's Quaternion.FromToRotation while working on this, and posted it here, and nobody said a damn thing. oh well didn't expect much tbf

    I also did a PEG (parsing expression grammar) algorithm as a separate project, that was really fun to make, and it's so incredibly powerful, and not just in a geeky way, that I have to do a programming game with it some day (a la Zachtronic), because it literally lets you build a domain-specific language of your own, in minutes, if you only manage to make a usable and fun-to-use tokenizer.

    but anyway, I'm going on tangents here, on my journeys, there were many many many situations, admittedly many of them low-level, when you simply have no other choice but to box and unbox, to trick the compiler into letting you do what you need to do, and basically as long as this doesn't happen on a critical path, there is no need to worry, cycle-wise. it's mostly just a superstition among the C# zealots.

    and overloading rarely helps with these kinds of situations, because you delegate the choice of a particular type to someone else, and have no control over it, so if it comes, it has to come through somewhere, and that somewhere has to be an object. if you think of it as of a colored gate, object has this idiotic rainbow color. and it's like that for a reason. so it's just a matter of when you'll unbox it, not whether you have to, you simply cannot clash two operand types in an operation unless you figure out what to do with the wrong one. but to discern which one is wrong you absolutely need to be able to tell which is which. it's the chicken or the egg. and obviously I'm not making statically typed languages, what would be the purpose of such a dry thing, I'm already in C#, right?

    in some other situations there is also option to use 'dynamic', but given that what I typically do is very low-level, and I tend to shave microseconds in my work, dynamic is not an option. I sometimes even inject and crash bits together and then build up my own floating number. I did that recently for a custom PCG random number generator, but I've yet to optimize that further, because oh boy modern compilers and processors are so well done, it turns out a simple calculation (including a division) was performing better than I could imagine, though I suspect I should try this again with unsafe pointers. I wanted it to be safe, it's probably the fastest (safe) thing I could come up with in C#.

    so yeah, I'm biased big time.

    consider this a sport of mine, it has no practical uses.
    Code (csharp):
    1. using System.Runtime.InteropServices;
    2.  
    3. namespace UsedToBeNamedProperly {
    4.  
    5.   //----------------------------------------------------------------------------------------
    6.   // here there be some low-level dragons:
    7.   // had to come up with a fast safe bit conversion for floating points
    8.  
    9.   // turns out that this is still a tenth of a microsecond slower than a division :/
    10.   // probably because of that final transformation (or because it really needs to be unsafe?)
    11.  
    12.   [StructLayout(LayoutKind.Explicit)] internal struct FPBitInjector {
    13.     [FieldOffset(0)] private float f;
    14.     [FieldOffset(0)] private double d;
    15.     [FieldOffset(0)] private uint i32;
    16.     [FieldOffset(0)] private ulong i64;
    17.  
    18.     private const uint  EXPN_S = 1U << 30;        // exponent 2^1
    19.     private const uint  MASK_S = (1U << 23) - 1;  // mantissa mask
    20.  
    21.     private const ulong EXPN_D = 1UL << 62;       // exponent 2^1
    22.     private const ulong MASK_D = (1UL << 52) - 1; // mantissa mask
    23.  
    24.     static FPBitInjector instance = new FPBitInjector();
    25.  
    26.     static public float InjectToFloatMantissa(uint bits, FPIntervalType fpInterval) {
    27.       // sets up the 23 mantissa bits so that the resulting IEEE754/32bit ends up in the range [2.0-4.0)
    28.       instance.i32 = bits & MASK_S | EXPN_S;
    29.       return (fpInterval == FPIntervalType.ZeroOne)? (instance.f * .5f - 1f) : (instance.f - 3f);
    30.     }
    31.  
    32.     static public double InjectToDoubleMantissa(uint bits1, uint bits2, FPIntervalType fpInterval) {
    33.       // sets up the 52 mantissa bits so that the resulting IEEE754/64bit ends up in the range [2.0-4.0)
    34.       // 20 high bits of bits2 are used for the 20 high bits of the result
    35.       instance.i64 = (((ulong)(bits2 & 0xfffff000) << 20) | bits1) & MASK_D | EXPN_D;
    36.       return (fpInterval == FPIntervalType.ZeroOne)? (instance.d * .5d - 1d) : (instance.d - 3d);
    37.     }
    38.  
    39.   }
    40.  
    41.   internal enum FPIntervalType {
    42.     ZeroOne = 0,     // [0 .. 1]
    43.     MinusPlusOne = 1 // [-1 .. +1]
    44.   }
    45.  
    46. }

    all in all, boxing/unboxing is really fast, unless you're overdoing it, whether in frequency or in quantity. which requires an ignorant mentality anyway. and I'm an optimist here, but the world is full of sloppy software anyway, you just cannot save them all.

    people do crazy things in unity, but whoever is thinking about casting an object, is at least a problem-solving type of a programmer, if somewhat lazy. here's hoping they'll learn in time what's really going on under the bonnet, and ultimately do it less.

    random ramblings #2
    man, I just hate that Unity has its random generator deep in the native binaries. that's just lame. my double is two times slower than their single. my single is 25% slower, and I cannot shave any more microseconds from it. my bounded uint is almost two times faster though, and I have a 64-bit(^2) seed and a custom xxHash ready to be used on a demanding procedurally-generated project. when I finish testing and all kinds of peripheral utilities, weighted choices, card drawers, dice rollers, and obviously an integrated RNG state manager (ffs it took them 10 years to let people save and reuse the RNG state, which I'd say was a conspiracy against free users), I intend to share this as well. why not?

    oh here's a story: once upon a time, I made a tiny LCG for Flixel, it was a finely crafted, easy-to-use pixel engine for Flash in its prime time. of course, I didn't invent that LCG, it's very hard to invent a proper PRNG. I just like to dig up pearls and had to contribute that because the leading guy (I think he made Overland recently) wasn't really sure how to implement a thing that could be seeded and worked just as distributed as the existing solution. he was thinking Mersenne twister.... as the fine admiral would say IT'S A TRAP...

    and here we are, there are hundreds of Flash games out there using that little piece of wisdom; it's funny to think that people all across the world unknowingly surfed on the patterns of that noise made by a random douche. ^_^
     
    asidjasidjiasd likes this.
  14. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,696
    That's some interesting stuff you've worked on.

    However, performance optimization wasn't a concern, at least not for me.

    And some of the projects and scenarios that you've mentioned are the type of scenarios where you receive / encounter different types of values, and it's more likely that type switching makes sense to some extent (even though you can still implement it in various ways, with or without type switching). Though, I cannot quite follow why low level performance optimization is suddenly part of the discussion.

    All of that aside, the purpose of my initial post in this thread was to find out whether he might be running into a more basic design problem, in order to encourage someone or myself to get him on the right track (if it turned out that he was), or at least post ideas on what could be done differently.

    And the only reason I posted is the missing context. This could be anywhere in his application. In a specific context or in a specific layer, this might be the most suitable approach, in others the most pragmatic, but there's also the chance that this was an unfortunate attempt to solve a problem for which a much better solution exists. Who knows...

    I mean, if you ask someone for the next railroad crossing, and you're told it is 1 mile north...
    Well yes, that's surely an accurate piece of information. That's the information that you've have asked for. Neither more nor less.

    Perhaps that's a place to meet someone. Great.
    Perhaps you wanna take some pictures. Great.
    Perhaps you just wanna cross safely. Bad luck I guess, there's an underpass a few yards in the other direction. You'll find out later. :p

    Fun aside, as I mentioned there's lots of room for discussions and suggestions. Most of the people that seek for help are also willing to improve their skills and learn new stuff, new concepts, new language features. If it doesn't help that specific person, it may help another...
     
    Last edited: Feb 23, 2020
    asidjasidjiasd likes this.
  15. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    725
    What's that saying "You seem to be a champion of lost causes" haha

    Nah, do your thing. As I said, everything you said is true. I just wish to not believe in any of it.
    I'd bet this guy knows what he's doing and that he's a helluva programmer.
    (just don't ask me how much I'd bet lol)

    It's a pretty common thing here, what you said, about the missing context. Not just here, everywhere in fact. People come and ask how do I jump off that cliff? I guess it's just popular like that. At first you take your time, elaborate intensely, wave your hands, and hand them over some safety instruction pamphlets. But after a while, you're like, well, you see that ticket stand over there, ok, stand in that line buddy, and when your time comes, geronimo.

    Rarely, but surely, someone would be knowledgeable enough to ask for a parachute as well. It's much better to waste time on these, imho, or to at least try to discern between them, even if you do make a mistake here and there, but I'm getting you're much younger than me. Giving everything and everyone a chance.

    In this case, just in this case, don't get me wrong, it's not my general opinion on such matters, I rarely generalize, I think it's better to steal this space for a casual conversation on life and simple wonders of the universe.

    Hey, don't be like that, you also said
    It didn't help that I was very silent about my projects for years. I've grown a zoo in that time. (I live in a very weird place, that got even weirder with time. And I really mean culturally; I miss talking with people on my level, this isn't even my native language, far from it. I'm lost in the East European darkness, like a whisper in the well, no that's too awfully poetic, more like a dumb rock on the wrong side of the Moon. It's probably too much, someone will surely come over and ban me for hijacking a thread.)
     
    asidjasidjiasd likes this.
  16. asidjasidjiasd

    asidjasidjiasd

    Joined:
    Feb 9, 2020
    Posts:
    12
    Exactly that, it is cleaner and more straightforward, anybody after me who will assess this code will have easier time understanding what I wrote. If I used typical
    if
    chain, it may add additional unneeded clutter in the code. That is not to say that
    if
    wouldn't solve it, but
    switch
    is far more compact and readable in my situation.

    Thank God for that. There are people here who have enough time and altruism to help someone who knows less without instantly shutting down questions labeling them as "off-topic" because they deem discussion around non-orthodox solutions as undesirable.

    Nope. I wish I was. But I am somewhat experienced coder, PHP/Node.js turned C# "programmer".
     
    orionsyndrome likes this.
unityunity