Is there any way I can cast an object at runtime to a specific type from a type name?
Or, more specific to my actual use case: I have a model, with typed properties, here eg Float32 and Vector3, create a channel out of it, then select properties via their path. Is there any way I can get this typed without creating a pad? It looks like the info is there? Or in other words, couldn’t there be a Select by Path which returns a typed channel/value instead of T?
Select (ByPath) outputs a channel, where the type is determined by the property type
In your scenario, this means: you still can treat the Channel as a Channel<object>, but it’s also a Channel<Float32> or Channel<Vector3> depending on which property you choose.
I might lack the full picture of what you are trying to do, but it sounds a bit like you are trying to mix things that don’t mix well. I think this is a pretty tricky lesson to learn and I think everybody is at times stumbling over it.
I am talking about distinguishing all things which can get tackled at compile-time already vs. stuff that has to be postponed to runtime.
Compile-time topics:
generic types, type parameters T
VL is trying to figure out types. Those that get displayed even when in stopped mode. Those are the so-called compile-time types.
Adaptive node implementations can get picked already
Runtime-time topics:
objects and ungeneric types
TypeSwitch making use of runtime type (which you get via object.GetType)
MakeGenericVLTypeInfo → CreateInstance
or even things like CastAs again testing for certain types at runtime
…
The compile-time part is there in order to make it easy to use the system and to already know which pins can be connected.
But sometimes your application is so “dynamic” that you don’t know at compile-time. So suddenly you need to make peace with the idea of working on another level. A level where you can’t know the type of your pad. It’s not float, it’s not Vector3. So you need to make it of type object in thus postpone that decision to point when the program is running.
I have seen different approaches to dealing with this situation over time.
If you want to, you as a user can make use of the non-generic part of the library, if you don’t know at compile-time which types will flow. And in other cases, it’s just more convenient to use the generic wrappers. Point being: you have the choice of which one to use. And it’s both supported.
In other cases, you will find libraries that only offer generic nodes and types. In those cases, you will need to instantiate them at runtime via reflection & by dynamically feeding the type arguments.
So, from library to library it’s a bit different how easy it is to postpone typing to the point in time where the program is already running. But what’s really important is: you need to be aware of what’s what. You can’t switch types with an LFO at runtime and would like the compile-time type to be switched in a pad. That’s just impossible.
Thank you for this detailed explanation @gregsn. It is a lot clearer now.
I’m sure this will help a lot of others too - it‘s obviously fundamental, but I didn’t really see/understand this difference between generics and objects just from trying to patch with them…
Ultimately this is what I really needed to hear. I came down this rabbit hole being almost convinced there must be some kind of voodoo trick to type a pad from a patch 😅 I can see now that this can’t work.
As a side note:
In 5.1 the node which returns a channel whose type is determined by the property the given path points to will be found in Select (ByPath Dynamic) .
Select (ByPath) will stay the same as it was in 5.0
As I understand it, is it applied somehow in this way?
But I get “Object” in the output. Is it possible to get a “String”? If not, advise a suitable way to convert quickly.
The compile time type of the output of Select (ByPath Dynamic) is Channel<Object>. At compile time we only know that the output is a Channel holding some Object. Connecting it to say a Channel<String> is not allowed, because it could be that the output holds something different than a String, it could contain a Banana.
We therefor need to test what the output actually contains. We can do this with the Cast nodes. In your case you know that the static string you feed to the node points to a property of type String, you can therefor HardCast the output to a Channel<String>. Should you refactor your patch and that type would change, the HardCast will throw an exception (turns pink) telling you that your assumption no longer is true.
We could also imagine that you wouldn’t know, for example you would let the string point to different things based on a variable, then you’d need to test with a CastAs node for Channel<String> or Channel<Banana>.
Another approach (and I believe that was kind of the initial question in this thread) is to ask the Channel<Object> of what runtime type it really is using the ClrTypeOfValues node. It would in your case tell you that it is a String, meaning you indeed have a Channel<String> in front of you. We could on the other hand also come up with an example where it would tell you that you have a Channel<Fruit> at hand, while the current value in that fruit channel is a Banana.
Now I forgot one important point: If you know where your path is pointing to and like in your case that will always be a String than there’s no point in using the dynamic version of the node, use instead the normal Select (ByPath) node where you connect a Channel<String> to its output and you’re good to go.
As also discussed in the thread Generic interfacesGeneric or even Adaptive constructs are very handy when juggling with types at compile-time or let’s call it construction-time of your program. Dynamic versions that deal with Objects, and try to make sense of them at runtime, can deal with cases where only at runtime the exact data type becomes clear (when asking the object for its type).
Interfaces and types that implement them form a type hierarchy. That’s true whether or not you try to work with types at compile time or at runtime. So I guess “impossible without the dynamic version”: No. It’s really about WHEN you want to reason about types.
Maybe unrelated, but as I see some cast nodes mentioned above: There is a new node CastAs (Channel), which might help you at some point. Not sure. Give it a go. As most Channel operators it is a bi-directional binding. When you have an upper fruit channel and CastAs to a banana channel: throwing a banana into one of those channels will make it appear in the bound channel. Hope that helps.