C# does the compiler leave re-references in the final binary?

69 views Asked by At

I have this code:

    private static void Tokenize(RichTextLabel Log,string commandString){
        Log.Text += "\n&";
        int[] tokens = new int[5];
        int current = 0;
        while (current < commandString.Length){
            switch (commandString[current]){
            case ' ':
                Log.Text += "+";
                break;
            default:
                Log.Text += "_";
                break;
            }
            current++;
        }
        Log.Text += "\n";
    }

And when i look at the "compiled code" (Low-level C# in rider's IL Viewer) which i know is not perfect or accurate in all cases. But this is what it produces:

    private static void Tokenize(RichTextLabel Log, string commandString)
    {
      RichTextLabel richTextLabel1 = Log;
      richTextLabel1.Text = string.Concat(richTextLabel1.Text, "\n&");
      int[] tokens = new int[5];
      for (int current = 0; current < commandString.Length; ++current)
      {
        if (commandString[current] == ' ')
        {
          RichTextLabel richTextLabel2 = Log;
          richTextLabel2.Text = string.Concat(richTextLabel2.Text, "+");
        }
        else
        {
          RichTextLabel richTextLabel3 = Log;
          richTextLabel3.Text = string.Concat(richTextLabel3.Text, "_");
        }
      }
      RichTextLabel richTextLabel4 = Log;
      richTextLabel4.Text = string.Concat(richTextLabel4.Text, "\n");
    }

As you can see, it produces 4 repetitive RichTextLabel richTextLabel = Log; My question is, is this accurate? If so why does it need to re-reference the object multiple times?

1

There are 1 answers

2
Charlieface On BEST ANSWER

This is mostly an accurate representation of what is going on, except that I think the variable doesn't actually exist.

The C# spec states:

12.21.4 Compound assignment

An operation of the form x «op»= y is processed by applying binary operator overload resolution (§12.4.5) as if the operation was written x «op» y. Then,

  • If the return type of the selected operator is implicitly convertible to the type of x, the operation is evaluated as x = x «op» y, except that x is evaluated only once (my bold).
  • Otherwise, ..... the operation is evaluated as x = (T)(x «op» y), where T is the type of x, except that x is evaluated only once.

And since the compiler doesn't know what Log does, to prevent the getter and setter potentially referring to different objects, it just caches it in a hidden variable.


Having said all that, looking at the generated IL in Sharplab shows that a dup opcode is used, so instead of caching it in a variable, it actually caches it in the stack. So Rider might just be generating a variable in order to represent the same semantics in psueudo-C#. The effect and performance should be the same though, and you shouldn't expect that the compiler will necessarily use one form over the other.