c# - Argument order for '==' with Nullable<T> -


the following 2 c# functions differ in swapping left/right order of arguments equals operator, ==. (the type of isinitialized bool). using c# 7.1 , .net 4.7.

static void a(isupportinitialize x) {     if ((x isupportinitializenotification)?.isinitialized == true)         throw null; } 

static void b(isupportinitialize x) {     if (true == (x isupportinitializenotification)?.isinitialized)         throw null; } 

but il code second 1 seems more complex. example, b is:

  • 36 bytes longer (il code);
  • calls additional functions including newobj , initobj;
  • declares 4 locals versus one.

il function 'a'…

[0] bool flag         nop         ldarg.0         isinst [system]isupportinitializenotification         dup         brtrue.s l_000e         pop         ldc.i4.0         br.s l_0013 l_000e: callvirt instance bool [system]isupportinitializenotification::get_isinitialized() l_0013: stloc.0         ldloc.0         brfalse.s l_0019         ldnull         throw l_0019: ret 

il function 'b'…

[0] bool flag, [1] bool flag2, [2] valuetype [mscorlib]nullable`1<bool> nullable, [3] valuetype [mscorlib]nullable`1<bool> nullable2         nop         ldc.i4.1         stloc.1         ldarg.0         isinst [system]isupportinitializenotification         dup         brtrue.s l_0018         pop         ldloca.s nullable2         initobj [mscorlib]nullable`1<bool>         ldloc.3         br.s l_0022 l_0018: callvirt instance bool [system]isupportinitializenotification::get_isinitialized()         newobj instance void [mscorlib]nullable`1<bool>::.ctor(!0) l_0022: stloc.2         ldloc.1         ldloca.s nullable         call instance !0 [mscorlib]nullable`1<bool>::getvalueordefault()         beq.s l_0030         ldc.i4.0         br.s l_0037 l_0030: ldloca.s nullable         call instance bool [mscorlib]nullable`1<bool>::get_hasvalue() l_0037: stloc.0         ldloc.0         brfalse.s l_003d         ldnull         throw l_003d: ret 

 

quesions

  1. is there functional, semantic, or other substantial runtime difference between a , b? (we're interested in correctness here, not performance)
  2. if not functionally equivalent, runtime conditions can expose observable difference?
  3. if are functional equivalents, b doing (that ends same result a), , triggered spasm? b have branches can never execute?
  4. if difference explained difference between appears on left side of ==, (here, property referencing expression versus literal value), can indicate section of c# spec describes details.
  5. is there reliable rule-of-thumb can used predict bloated il @ coding-time, , avoid creating it?

      bonus. how respective final jitted x86 or amd64 code each stack up?


[edit]
additional notes based on feedback in comments. first, third variant proposed, gives identical il a (for both debug , release builds). sylistically, however, c# new 1 seem sleeker a:

static void c(isupportinitialize x) {     if ((x isupportinitializenotification)?.isinitialized ?? false)         throw null; } 

here release il each function. note asymmetry a/c vs. b still evident release il, original question still stands.

release il functions 'a', 'c'…

        ldarg.0         isinst [system]isupportinitializenotification         dup         brtrue.s l_000d         pop         ldc.i4.0         br.s l_0012 l_000d: callvirt instance bool [system]isupportinitializenotification::get_isinitialized()         brfalse.s l_0016         ldnull         throw l_0016: ret 

release il function 'b'…

[0] valuetype [mscorlib]nullable`1<bool> nullable, [1] valuetype [mscorlib]nullable`1<bool> nullable2         ldc.i4.1         ldarg.0         isinst [system]isupportinitializenotification         dup         brtrue.s l_0016         pop         ldloca.s nullable2         initobj [mscorlib]nullable`1<bool>         ldloc.1         br.s l_0020 l_0016: callvirt instance bool [system]isupportinitializenotification::get_isinitialized()         newobj instance void [mscorlib]nullable`1<bool>::.ctor(!0) l_0020: stloc.0         ldloca.s nullable         call instance !0 [mscorlib]nullable`1<bool>::getvalueordefault()         beq.s l_002d         ldc.i4.0         br.s l_0034 l_002d: ldloca.s nullable         call instance bool [mscorlib]nullable`1<bool>::get_hasvalue() l_0034: brfalse.s l_0038         ldnull         throw l_0038: ret 

lastly, version using new c# 7 syntax mentioned seems produce cleanest il of all:

static void d(isupportinitialize x) {     if (x isupportinitializenotification y && y.isinitialized)         throw null; } 

release il function 'd'…

[0] class [system]isupportinitializenotification y         ldarg.0          isinst [system]isupportinitializenotification         dup          stloc.0          brfalse.s l_0014         ldloc.0          callvirt instance bool [system]isupportinitializenotification::get_isinitialized()         brfalse.s l_0014         ldnull          throw  l_0014: ret  

looks 1st operand converted 2nd's type purpose of comparison.

the excess operations in case b involve constructing nullable<bool>(true). while in case a, compare true/false, there's single il instruction (brfalse.s) it.

i couldn't find specific reference in c# 5.0 spec. 7.10 relational , type-testing operators refers 7.3.4 binary operator overload resolution in turn refers 7.5.3 overload resolution, latter 1 vague.


Comments

Popular posts from this blog

Is there a better way to structure post methods in Class Based Views -

performance - Why is XCHG reg, reg a 3 micro-op instruction on modern Intel architectures? -

jquery - Responsive Navbar with Sub Navbar -