I'm trying to understand what the compiler is doing to very simple piece of code:
if (group.ImageHeight > 1 && group.ImageWidth > 1)
{ //No code exists between the braces
}
After compiling in Debug
configuration, then decompiling I see this:
if (group.ImageHeight <= 1 || group.ImageWidth <= 1);
Decompiling a Release
configuration results in
if (group.ImageHeight > 1)
{
int imageWidth = group.ImageWidth;
}
More complete (original) code:
public class Group
{
public int ImageHeight { get; set; }
public int ImageWidth { get; set; }
}
//The following belongs to a different project than `Group`
static void Main(string[] args)
{
Group group = new Group();
MyMethod(group);
}
static void MyMethod(Group group)
{
if (group.ImageHeight > 1 && group.ImageWidth > 1)
{
}
}
Here are my guesses and observations so far:
group
type belongs to another project in my solution. I say this because the compiler probably can't "know" what the side effects of evaluating the properties could be in the future. For instance, I could, after compiling, replace the DLL that contains the definition for group
.Release
config the possible side effects appear to be the same as my code: ImageHeight
is evaluated and if meets the > 1
condition will evaluate ImageWidth
(although through assignment rather than comparison)Now, for my specific questions:
Release
config use an assignment (int imageWidth = group.ImageWidth
) rather than my original comparison? Is it faster to run an assignment?Debug
configuration completely change the possibility of side effects? In this configuration both ImageHeight
and ImageWidth
will always be evaluated. For the first specific question. When you look at IL on sharplab.io The simple assignment is 1 compare instruction short. Whose "then" and "else" would point to the same instruction (in this case IL_0012) so compare there is not needed for calling function and two pops are enough. Weird is only loading the Int32 constant 1 which will be discarded immidiately.
if (group.ImageHeight > 1)
IL_0000: ldarg.0
IL_0001: callvirt instance int32 Group::get_ImageHeight()
IL_0006: ldc.i4.1
IL_0007: ble.s IL_0012
int imageWidth = group.ImageWidth;
IL_0009: ldarg.0
IL_000a: callvirt instance int32 Group::get_ImageWidth()
IL_000f: ldc.i4.1
IL_0010: pop
IL_0011: pop
IL_0012: ret
For second specific question. If you look at IL on the same page with Debug mode, you'll see, that the code is identical only with some additional instructions for debuging and the compare itself so you can watch the result of it in debuger.
IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt instance int32 Group::get_ImageHeight()
IL_0007: ldc.i4.1
IL_0008: ble.s IL_0015
IL_000a: ldarg.0
IL_000b: callvirt instance int32 Group::get_ImageWidth()
IL_0010: ldc.i4.1
IL_0011: cgt
IL_0013: br.s IL_0016
IL_0015: ldc.i4.0
IL_0016: stloc.0
// sequence point: hidden
IL_0017: ldloc.0
IL_0018: brfalse.s IL_001c
IL_001a: nop
IL_001b: nop
IL_001c: ret