Interesting Reflection Question

So here’s an odd one:
Is there a way to detect, with reflection, the different ways an object is being cast in a script file? For instance:

I’ve got an enum here:

public enum VariableType {Int, Float, String};

…and a class:

public class GenericVariable {

    // list of the various ways this instance is cast
    VariableType[] types;

    // implicit operators for int, float, and string would go here
}

…and that class being used in code:

// instance to be cast
GenericVariable variable;

void AssignGenericVariable() {

    // implicit casting of instance
    variable = 0;
    variable = 1.5f;
    variable = "string";

    int num = variable;
    float moreNum = variable;
    string words = variable;
}

…What I’d like to do is build up “variable” behind the scenes, using reflection. I’d like to crawl through the last code example, and find out all the ways “variable” is being implicitly cast, so I can fill out it’s “types” array. Any ideas how this could be done?

Many thanks for any pointers!

Frankly, I’m not sure what Reflection has to do with anything here.

public sealed class GenericVariable
{
    private object value = null;

    public Type Type
    {
        get { return value.GetType(); }
    }

    private void Validate(Type type)
    {
        if (value == null)
             throw new NullReferenceException();

        if (value.GetType() != type)
             throw new InvalidTypeException();
    }

    public static implicit operator string(GenericVariable v)
    {
        v.Validate(typeof(string));
        return (string)v.value;
    }

    public static implicit operator int(GenericVariable v)
    {
        v.Validate(typeof(int));
        return (int)v.value;
    }

    public static implicit operator float(GenericVariable v)
    {
        v.Validate(typeof(float));
        return (float)v.value;
    }

    public static implicit operator GenericVariable(string s)
    {
        return new GenericVariable(s);
    }

    public static implicit operator GenericVariable(int i)
    {
        return new GenericVariable(i);
    }

    public static implicit operator GenericVariable(float f)
    {
        return new GenericVariable(f);
    }

    public GenericVariable(string s)
    {
        value = s;
    }

    public GenericVariable(int i)
    {
        value = i;
    }

    public GenericVariable(float f)
    {
        value = f;
    }
}

Is this what you’re after? A kind of type-less variable container?

EDIT: Oh, I see what you did there. But I don’t see why you would want to do such a bad code design. What happen if someone assign 2 ints to the same container? Should it works as a list? Or is each specific type unique?

Frankly, I think you should explain why you would want such thing and we may give you alternatives.

Thanks for the help. Ok, why do I want to do this? It sounds crazy but there is a reason:

I’m writing an editor extension- a visual scripting tool. It consists of Nodes, with positions on a 2D graph. Each Node has named connections, which look like tiny boxes around each node, and store information about how nodes link together. These links are created when the user manually drags a line between the connections of two nodes.

Some of the these connections act as variable inputs for a node, and might be named, for example, “Target”. Variable input connections only accept links with Nodes of a specific kind(“Variable Nodes”), and then, only Variable Nodes that contain a specific Type variable.“Target”, for example, may only accept links with “Float Variable Nodes”.

BUT! In some cases, “Target” may need to accept links from multiple variable types, say both a “Float Variable Node” and a “Int Variable Node”. This is simply for convenience, and to keep nodes from ballooning in visual size due to having too many surrounding Connections.

If you’re still with me, bravo. Here’s where it hopefully wraps up: Included as a feature of the visual scripting editor is an API which makes it easy to write your own Nodes. Each node equates to a script file, with a Coroutine for every “input” connection. I don’t want users to have to define their connections as variables in the class. instead, I’d like to create each Connection for a node behind the scenes, when it’s created by the user in the 2D graph view. To do this, I’ll need a way to grab various information from Node classes. for example if a node script contains the following Coroutine:

public IEnumerator Start(VariableConnection Target) {

    Target = 10;
    string text = Target;

    Fire("Out");
}

I’ll need to parse that the Node should have three connections:

  1. “Start” (Input Connection that’s triggered from another Node)
  2. “Target” (Variable Connection that accepts links from int Nodes and string Nodes)
  3. “Out” (Output Connection that links to another Node connection)

Right now I’m focusing on #2, trying to see if it’s even possible. I can tell that there needs to be a Variable Connection named “Target”, from the Coroutine’s arguments, but now I need to determine it’s accepted link types. I’m looking for a way to crawl through a method like the one above, and pick out that “Target” has been cast as a string, and assigned as an int.

Then I can use that information to say “Target” accepts links from int Nodes and string Nodes.

If by some miracle you’re still reading, thank you. Maybe what I’m trying to do does make some sort of sense.

Seriously, I would question just about every single choice you made in this matter so far.

Reflecting how something is cast from one type to another internally makes no sense at all. In the example you gave, at the line #6, “Target” is what? The value received by the node, or was it overwritten by “10”? And if it was cast towards a string, how is it important to the input nodes? How is the one writing that should know what’s going on?

Just so you know, there’s quite a few node-based solution out there.

Your idea of using a method as a node has just about one major flaw… Every internal local variable are destroyed when the method exits its scope.

Why don’t you have it class-based as most people do? You got have to one major reason to try to go method-based.

public abstract class Node
{
    public abstract void Run();
    public virtual bool Validate() { }
    public virtual void UpdateOutput() { }
}

Example of a node;

public class Addition : Node
{
    [Input(ConnectionType.Number)]
    public Connection InputA;
    [Input(ConnectionType.Number)]
    public Connection InputB;
   
    [Output(ConnectionType.Number)]
    public Connection Output;

    public override void Run()
    {
        Output = InputA.value + InputB.value;
    }

    public override bool Validate()
    {
        return (InputA != null  InputB != null);
    }
}

The point here is, it’s the machine in the background that know which node is running and making sure nodes have all their variable up to date before firing them.

My setup is very similar to your example. My Nodes are class based, with methods (in my case, Coroutines) for each input connection. And by “input connection”, I mean connections on the left side of a node, which trigger it’s various actions. Guess I did a poor job of explaining it.

None of my connections exist only as local variables of methods. As your described (and with no insult intended), obviously that wouldn’t work. However, the methods I used for each Input Connection can take Variable Connections as parameters. This is a wholly unnecessary approach, but it makes it possible to avoid defining the Variable Connections elsewhere- you can just extract them from the method parameters using Reflection, and create the needed global Connections behind the scenes. This is done before runtime, when the node is placed in editor. Many of the existing VS editors, such as UScript, use this or a similar approach.

One thing UScript doesn’t do is allow a single Variable Connection to accept multiple variable types. This can be useful/convenient, for example, in a “Print Text” node that can handle ints, floats, strings, etc. from a single “To Print” variable connection.

In my project, this works through some implicit casting. You can assign a Variable Connection a value of any type, and cast it as any type. It handles any conflicts internally and only assigns/returns values in cases where it’s possible.

What I haven’t figured out how to do (and indeed don’t know if it’s possible) is to determine all the types that must be supported by a Variable Connection just by seeing how it’s used in code. This would be needed, because in the editor, the user shouldn’t be able to hook up a “string” variable node to an “int-only” variable Connection. So for now I’m still requiring everything be explicitly defined in the script file.

It was nothing more than a fun experiment, to see if I could add an extra bit of convenience to the user/see if something interesting was possible.

Why would you want a method per connection? Isn’t a connection a value?

Anyway, I doubt you could implicitly decide what kind of variable it is. You’ll always want some attributes tagged it to reduce the scope of the variables.

[ConnectionType(Type.Number | Type.String | Type.Object)]

In my project, connection doesn’t always == value. I have three types:

-Input Connections (left side of the node, used to trigger the behavior of the node)
-Variable Connections (Underside of the node, used to plug in values)
-Output Connections (Right side of the node, used to continue the circuit by connecting to the Input Connections of other nodes).

In each Node class, each Input Connection has a corresponding IEnumerator. When an InputConnection is triggered, its Coroutine is called. There’s no main “Run” method, and nothing is called via a master Update function somwhere. It’s certainly not the only approach- I chose it for a few organization/perf benefits I liked.

For the Variable Connection types, I ended up using an array of enums instead of attributes. Certainly both would work fine- I only went this route to facilitate the behind-the-scenes Connection building I was after. :slight_smile:

You have no output variables? Or does your output connection also serves as carrying the resulting values around?

You can’t query the implementation of a method using reflection, only its signature. You could inspect the IL but thats a lot of effort (I expect to get something that works most of the time, but even then still causes node creators quite a bit of frustration, would be siginificantly more effort than writing the rest of the system).

I think its better to encourage your users to write nodes that have a signature that reflects their intent (e.g. by getting rid of your VariableConnection or by refining your VariableConnection class or by using annotations).

Or from another perspective… why is it okay to force your users to write their node actions using IEnumerators and your VariableConnection class, but not okay to expect that they follow a certain pattern when defining their inputs/variables?

@LightStriker: My Variable Connections double as inputs and outputs for values.

@JohnnyA: I think you’re right, and I’m abandoning this experiment. It was a fun idea, to see just how minimal the Node syntax could be. But as you said, there’s a questionable payoff. I’ll stick with what I’ve got now, in which Connections are explicitly declared as script fields, with attributes.

I wrote an editor wizard that generate skeleton versions of new Node files for you anyhow, so the syntax matters even less. Oh well. Thanks a lot for reading through everything and responding.