Search code examples
c#refcreateinstance

Passing parameters by reference in CreateInstance


[Edit: I've added a lot of detail to this question to make it clearer why I need to pass the enumerator by reference]

I'm writing some code that parses a list built up of commands and parameters. Not all commands have the same number of parameters. For instance, the list could be -

command1,
100,
command2,
54,
42,
71,
command3,
10,
31,
command1,
82,
command3,
102,
87

(Note that some commands may have parameters that are non-integers)

I'm iterating through the list of data using a List enumerator. Each command has its own class that is able to parse the command's parameters from the list (and do a series of other activities relating to the command which aren't needed for this cut down example).

I have a dictionary which connects the commands to their classes -

var map = new Dictionary<string, Type>
{
    { "command1", typeof(Command1Class) },
    { "command2", typeof(Command2Class) },
    { "command3", typeof(Command3Class) },
};

So my basic parsing loop is as follows -

var enumerator = data.GetEnumerator();
while (enumerator.MoveNext())
{
    var command = (string)enumerator.Current;
    codeBlock.AddBlock((Block)Activator.CreateInstance(map[command], new object[] { enumerator }));
}

(All command classes derive from the same base type, Block)

So in the constructor of each command class, it can parse how ever many parameters that command takes and, upon return to the main loop, the enumerator will have moved on through those parameters.

For example -

class Command1Class : Block
{
    string param;

    public Command1Class(ref List<object>.Enumerator enumerator)
    {
        enumerator.MoveNext();
        param = (string)enumerator.Current;
    }
}

But what I'm finding it that the enumerator is only modified locally in the constructor. So upon returning from the constructor, the enumerator is still pointing to the command rather than having moved on through how ever many parameters that command requires.

If I do this non-dynamically using the following style, it works as expected with the enumerator pointing to the next command when it returns from the constructor -

new SomeClass(ref enumerator)

So I'm wondering why my code with CreateInstance is not working as expected and how am I able to do this dynamically?


Solution

  • You have quite many issues with your approach because Enumerator is struct (so, value type). It means (among other things) that it gets copied under many circumstances, for example if you remove ref keyword:

    public Command1Class(List<object>.Enumerator enumerator)
    

    And then pass your enumerator - it will be copied and enumerator inside Command1Class constructor is not the same instance as outside. You noticed this and pass enumerator by reference in constructor, but the same problem bites you again when you do this:

    (Block)Activator.CreateInstance(map[command], new object[] { enumerator })
    

    You pass enumerator inside object[] array (as expected by CreateInstance), and this again makes a copy. It's more clear like this:

    var args = new object[] { enumerator };
    Activator.CreateInstance(typeof(Command1Class), args);
    

    When you create args array - it already contains a copy of enumerator, not the same instance. Now this copy is passed by reference to your constructor and is indeed advanced, which you can verify like this:

    var args = new [] { enumerator };
    Activator.CreateInstance(typeof(Command1Class), args);
    // assign copy back to original value
    enumerator = (List<object>.Enumerator) args[0];  
    // now enumerator is advanced as expected
    

    Actually the same will happen if enumerator were reference type, because when you put it in array to pass arguments to Activator.CreateInstance - it no longer the same variable (it points to the same object in memory, but pointer itself is different).

    So your approach is very fragile and probably you should redesign it. If you'd like to stick with it - do not pass anything to constructor but instead create method at Block class:

    public void Init(ref List<object>.Enumerator enumerator)
    

    Then call it without reflection.