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
- is there functional, semantic, or other substantial runtime difference between a , b? (we're interested in correctness here, not performance)
- if not functionally equivalent, runtime conditions can expose observable difference?
- if are functional equivalents, b doing (that ends same result a), , triggered spasm? b have branches can never execute?
- 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. - 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
Post a Comment