Tidbits

Things I didn’t know until checking the IL

I have grown the habit of doing a couple of things when writing code that I initially thought would improve performance, even if only by a teensy tiny bit.
Well, turns out most of that was pretty much useless. Not hurting anything, but not helping either.
How did I learn that? By having a look at the IL. And here’s a collection of things I found out, that I didn’t know about before.

The code was compiled in Visual Studio for the .NET Framework 4.8 with MSBuild 16.2.0 and the Optimize flag set to true.
The IL was read with the IL Viewer of JetBrains ReSharper.


i++ or ++i

My assumption always was, that the later is slightly faster, since the old value doesn’t need to be “remembered” for use in whatever context it’s used in. Turns out, not really.
These two lines result in the exact same IL:

++foo;
foo++;

If there is a bit more going on, the code actually has a difference:

foo = ++foo;
    IL_0002: ldloc.0      // foo
    IL_0003: ldc.i4.1
    IL_0004: add
    IL_0005: dup
    IL_0006: stloc.0      // foo
    IL_0007: stloc.0      // foo
foo = foo++;
    IL_000c: ldloc.0      // foo
    IL_000d: dup
    IL_000e: ldc.i4.1
    IL_000f: add
    IL_0010: stloc.0      // foo
    IL_0011: stloc.0      // foo

The “remembering” part is simply the duplication moved further up, before the increment.
Same operations, different order.
The only real difference this makes, is that the first option has at most 2 elements on the stack, the second 3. Slight win for my preference? But generally not important. Especially since what ever else you’re doing is probably going to need a bigger stack anyway.


Variable declarations inside or outside of loops

Not sure where I got this from, but basically I used to always declare variables outside of a loop, even if I only need it inside. I think the idea in my head was, that declaring it would require resources at that point in the program, and declaring it inside a loop would cost me those resources every iteration.
Nope.

while (true)
{
    string line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}

string line;
while (true)
{
    line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}

Both result in the same IL … until you add a second loop.

string line;
while (true)
{
    line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}
while (true)
{
    line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}

while (true)
{
    string line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}
while (true)
{
    string line = Console.ReadLine();
    Console.WriteLine($"Input was: {line}");
 
    if (line == "exit")
        break;
}

Declaring line outside the loop means the exact same variable is used both times, or better, the exact same memory address. In the second example the compiler ignores that option and uses two different variables, one for each loop.
I suspect that at runtime the JIT compiler, which does a lot more optimization, sees the obvious potential for reuse and makes the IL difference meaningless.
I do tend to declare variables inside the loop nowadays, unless I explicitly want them kept “alive” beyond their scope.


for or foreach

Let’s assume you got an array and want to iterate through it. Do you use a for or foreach loop?
I used to think foreach adds overhead, because it treats the array as an IEnumerable and creates an Enumerator.
It doesn’t.
These two loops

for (int i = 0; i < bar.Length; ++i)
    sum += bar[i];
foreach (int i in bar)
    sum += i;

do look different in the IL, but they do the same basic thing.
The foreach loop uses a variable it increments each iteration to use as index for the array.
Once that index reaches the arrays length, the loop ends. Pretty much like the for loop. This only applies to arrays though, List for example has an indexer, but foreach will use the enumerator!


! is null or is object

In the new C# versions there is a neat language addition, but if var is not null is not an option for you, there is either

if (!(something is null))

or

if (something is object)

Both have the same effect. But does the later do more stuff? No. Same IL.

    IL_0006: ldloc.0      // something
    IL_0007: brfalse.s    IL_000e

Same with these two.

var = !(something is null);
var = something is object;
    IL_0006: ldloc.0      // something
    IL_0007: ldnull
    IL_0008: cgt.un
    IL_000a: stloc.1      // var

switch or else if

If you have several different branches depending on a single value, you often use a switch. But what if there are only two options? Would an if/else if have a better performance? Short answer: Maybe, but don’t bother. Because the compiler makes that choice for you already.
Actually, switch is quite often compiled into something closer to if/else if, you just get special switch IL when you have neatly arranged integer values. And even then there are some interesting constellations, like splitting your switch into several individual ones.
So when a switch makes your code more readable, or simply easier to expand in the future, just use a switch.


Repeat typeof or store Type

nameof is evaluated during compilation, and all that remains in the compiled code is a string constant. That means I can use it as often as I want, without worrying about performance.
How about typeof though? What does it do in the IL? If I reference the type several times, can I still reuse typeof or is it better to cache the value?

string typeFullName = typeof(A).FullName;
bool typeIsEnum = typeof(A).IsEnum;
bool typeIsSerializable = typeof(A).IsSerializable;
    // [14 13 - 14 54]
    IL_0000: ldtoken      IlCode.A
    IL_0005: call         class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    IL_000a: callvirt     instance string [mscorlib]System.Type::get_FullName()

    // [15 13 - 15 48]
    IL_000f: ldtoken      IlCode.A
    IL_0014: call         class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    IL_0019: callvirt     instance bool [mscorlib]System.Type::get_IsEnum()
    IL_001e: stloc.0      // typeIsEnum

    // [16 13 - 16 64]
    IL_001f: ldtoken      IlCode.A
    IL_0024: call         class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    IL_0029: callvirt     instance bool [mscorlib]System.Type::get_IsSerializable()
    IL_002e: stloc.1      // typeIsSerializable

As you can see, each typeof results in a method call to Type.GetTypeFromHandle. Storing the result seems the better option to me.

Type type = typeof(A);
string typeFullName = type.FullName;
bool typeIsEnum = type.IsEnum;
bool typeIsSerializable = type.IsSerializable;
    // [14 13 - 14 35]
    IL_0000: ldtoken      IlCode.A
    IL_0005: call         class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)

    // [15 13 - 15 49]
    IL_000a: dup
    IL_000b: callvirt     instance string [mscorlib]System.Type::get_FullName()
    IL_0010: stloc.0      // typeFullName

    // [16 13 - 16 43]
    IL_0011: dup
    IL_0012: callvirt     instance bool [mscorlib]System.Type::get_IsEnum()
    IL_0017: stloc.1      // typeIsEnum

    // [17 13 - 17 59]
    IL_0018: callvirt     instance bool [mscorlib]System.Type::get_IsSerializable()
    IL_001d: stloc.2      // typeIsSerializable

Good thing I did it this way already!