1

In C#, why assign member variables to local variables and then use local variables instead of using member variables directly, as shown in the following snippet:


internal class LocalVariableTest
{
    private readonly int[] _items = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };


    public void ConsoleWriteLine()
    {
        var items = _items;

        foreach (var item in items)
        {
            System.Console.WriteLine(item);
        }
    }

    public void ConsoleWriteLine2()
    {
        foreach (var item in _items)
        {
            System.Console.WriteLine(item);
        }
    }
}

What is the difference between ConsoleWriteLine and ConsoleWriteLine2?

I would like to know if there is any difference between the two methods after compiling. I have seen many places that have this way of writing. Which one has higher performance and where is the knowledge about this, such as Microsoft Learn?

knab
  • 21
  • 3
  • 3
    Is this a real snippet or a made up example? There are reasons to do this sometimes, but *in this case* I found no difference in the resulting assembly code at all – harold Dec 17 '22 at 15:01
  • "_I have seen many places that have this way of writing._" It's not improving readability, it's not ensuring thread-safety (in case the array is manipulated and accessed concurrently), it's not simplifying code. This form of code you posted where the value of a readonly field itself is simply assigned to a local variable like that and nothing else is pretty much pointless... –  Dec 17 '22 at 15:04
  • 1
    @MySkullCaveIsADarkPlace it (ie the idea of copy-to-local in general) should not be dismissed entirely, there are other cases in which it helps. For example [here](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKGIGYACMhgYQYG8aHuGAHKAJYA3bBhgNY2ACYQAdgBsAngwGyMAbQC6DbFCjZFAbi48T3ekxQMAsuQAUASg5me3AGbQGd1RhUMAvAwADIZ+ADwM5EEhKgDUsQ4urq66+orqAtqBAsbUrgC+Li4WxFbWpI7OecncIlAM8hBg2PIBOnoGuTXunt5qfoExAgwRUUPxidXdPI3N8hlZKl08hdT5QA===) where the version with copy-to-local has a form of bounds check elimination – harold Dec 17 '22 at 15:17
  • "I have seen many places that have this way of writing." the link [https://github.com/kerryjiang/SuperSocket/blob/master/src/SuperSocket.Channel/PipeChannel.cs](https://github.com/kerryjiang/SuperSocket/blob/master/src/SuperSocket.Channel/PipeChannel.cs). The methods FillPipeAsync, ProcessSends, ReadPipeAsync ... – knab Dec 17 '22 at 16:05
  • @harold, that's interesting. Thanks for sharing! (I guess, although i am just speculating, that might indicate that readonly fields are not really read-only on the IL/CLR level. Hmm, i have to try setting such a readonly field through reflection and see what happens...) –  Dec 17 '22 at 16:16
  • 1
    @MySkullCaveIsADarkPlace indeed readonly fields can be modified via reflection (and other means, unsafe code certainly, and probably via a `[StructLayout(LayoutKind.Explicit)]` trick) (and if it's a field in a struct, [readonly is a lie](https://stackoverflow.com/a/6063546/555045)) – harold Dec 17 '22 at 16:55

1 Answers1

1

Any difference? barely.

The compiler should optimize it to be approximately the same.

Here, the difference in debug mode is two extra instructions and an additional variable on the stack. Will that be noticeable? Only way to know for sure is to run a benchmark with something like benchmark.net.

As pointed out in the comments, your code in release mode is identical.

You can view the difference with ILSpy, or an online equivalent like sharplab

// Methods
    .method public hidebysig 
        instance void ConsoleWriteLine () cil managed 
    {
        // Method begins at RVA 0x2094
        // Code size 39 (0x27)
        .maxstack 2
        .locals init (
            [0] int32[] items,
            [1] int32[],
            [2] int32,
            [3] int32 item
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldfld int32[] LocalVariableTest::_items
        IL_0007: stloc.0
        IL_0008: nop
        IL_0009: ldloc.0
        IL_000a: stloc.1
        IL_000b: ldc.i4.0
        IL_000c: stloc.2
        // sequence point: hidden
        IL_000d: br.s IL_0020
        // loop start (head: IL_0020)
            IL_000f: ldloc.1
            IL_0010: ldloc.2
            IL_0011: ldelem.i4
            IL_0012: stloc.3
            IL_0013: nop
            IL_0014: ldloc.3
            IL_0015: call void [System.Console]System.Console::WriteLine(int32)
            IL_001a: nop
            IL_001b: nop
            // sequence point: hidden
            IL_001c: ldloc.2
            IL_001d: ldc.i4.1
            IL_001e: add
            IL_001f: stloc.2

            IL_0020: ldloc.2
            IL_0021: ldloc.1
            IL_0022: ldlen
            IL_0023: conv.i4
            IL_0024: blt.s IL_000f
        // end loop

        IL_0026: ret
    } // end of method LocalVariableTest::ConsoleWriteLine

    .method public hidebysig 
        instance void ConsoleWriteLine2 () cil managed 
    {
        // Method begins at RVA 0x20c8
        // Code size 37 (0x25)
        .maxstack 2
        .locals init (
            [0] int32[],
            [1] int32,
            [2] int32 item
        )

        IL_0000: nop
        IL_0001: nop
        IL_0002: ldarg.0
        IL_0003: ldfld int32[] LocalVariableTest::_items
        IL_0008: stloc.0
        IL_0009: ldc.i4.0
        IL_000a: stloc.1
        // sequence point: hidden
        IL_000b: br.s IL_001e
        // loop start (head: IL_001e)
            IL_000d: ldloc.0
            IL_000e: ldloc.1
            IL_000f: ldelem.i4
            IL_0010: stloc.2
            IL_0011: nop
            IL_0012: ldloc.2
            IL_0013: call void [System.Console]System.Console::WriteLine(int32)
            IL_0018: nop
            IL_0019: nop
            // sequence point: hidden
            IL_001a: ldloc.1
            IL_001b: ldc.i4.1
            IL_001c: add
            IL_001d: stloc.1

            IL_001e: ldloc.1
            IL_001f: ldloc.0
            IL_0020: ldlen
            IL_0021: conv.i4
            IL_0022: blt.s IL_000d
        // end loop

        IL_0024: ret
    } // end of method LocalVariableTest::ConsoleWriteLine2
BurnsBA
  • 4,347
  • 27
  • 39
  • 1
    In Release mode, sharplab will even show the exact same produced code. – Klaus Gütter Dec 17 '22 at 15:03
  • 1
    Thank you very much for your help. I seem to have found the question I want to ask, the link [https://stackoverflow.com/questions/8229585/in-c-does-copying-a-member-variable-to-a-local-stack-variable-improve-performa](https://stackoverflow.com/questions/8229585/in-c-does-copying-a-member-variable-to-a-local-stack-variable-improve-performa). I searched the question on the search engine before, but I don't seem to know how to describe my idea – knab Dec 17 '22 at 15:48