Search code examples
c#reflectioncilreflection.emit

Getting value from top of IL stack using IL Emit


I have a LocalBuilder, which is essentially an array. I can use it in IL just fine, and I can load it's length using OpCodes.Ldlen. I was just wondering, if there is any way to get the length from top of stack to some actual variable. I am looking for something like

int lengthVariable = 0;

IL.Emit(OpCodes.Ldloc, arr);
IL.Emit(OpCodes.Ldlen);
IL.Emit(??????, lengthVariable);

I want to get this variable so that I can run a loop based on array's length. I know I can create a loop in IL, but I thought it would be a lot more convenient if this was possible.

Edit: What I'm trying to do over here is

  • Call an external method (which returns array).
  • Perform some action on all the elements of that array. Currently, I am doing this by having two copies of of array (IL and non-IL). Using the non-IL copy, I get the length, and then perform the action n number of times.

The problem with this is that I now have to call the external method twice. I was hoping that I could get the length from IL array so that I can loop over it directly, without calling the external method twice. I know I can write a for loop in IL, but I was kind of avoiding writing branching statements of IL.


Solution

  • You can't populate your local lengthVariable like that - it runs in a completely separate scope / stack-frame. However, you could change your method (DynamicMethod or MethodBuilder) to return it, then create a delegate to your new method as a Func<int>, and invoke it.

    Then your last line would be IL.Emit(Opcodes.Ret);, to return the single value on the local stack. Alternatively, you could store the value into an instance or static field, with Opcodes.Stfld or Opcodes.Stsfld.


    Following discussion in comments, it seems that the remark

    I know I can write a for loop in IL, but I was kind of avoiding writing branching statements of IL.

    in the question may be surmountable; foreach isn't really all that tricky - the final IL you're after is obtainable by decompiling existing code, which leaves the only really tricky bit the handling of labels for the actual branch targets - but that just means calling .DefineLabel() to declare them - you can use them as targets before you know where they'll be jumping to - and .MarkLabel() to position them (once only). It isn't quite direct IL (it uses an abstraction layer), but you can see this approach being used here - in particular, note that it uses DefineLabel() ahead of time, and marks the destinations later at MarkLabel.