带有Nullable< T>的'=='的参数顺序 [英] Argument order for '==' with Nullable<T>
问题描述
以下两个C#
函数的区别仅在于将参数的左/右顺序交换为等于运算符==
. (IsInitialized
的类型是bool
).使用 C#7.1 和 .NET 4.7 .
The following two C#
functions differ only in swapping the left/right order of arguments to the equals operator, ==
. (The type of IsInitialized
is bool
). Using C# 7.1 and .NET 4.7.
static void A(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized == true)
throw null;
}
static void B(ISupportInitialize x)
{
if (true == (x as ISupportInitializeNotification)?.IsInitialized)
throw null;
}
但是第二个代码的 IL代码似乎要复杂得多.例如, B 是:
But the IL code for the second one seems much more complex. For example, B is:
- 长36个字节(IL代码);
- 调用其他功能,包括
newobj
和initobj
; - 宣布四个本地人,而只宣布一个.
- 36 bytes longer (IL code);
- calls additional functions including
newobj
andinitobj
; - declares four locals versus just one.
[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
函数"B"的IL ...
[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
- A 和 B 之间在功能,语义或其他运行时方面是否有区别? (我们只对此处的正确性感兴趣,而不对性能感兴趣)
- 如果它们在功能上不是等价的,那么哪些运行时条件可以暴露出明显的差异?
- 如果它们是功能等效项,则 B 在做什么(总是以与 A 相同的结果结束),以及触发了什么它的痉挛? B 是否具有永远无法执行的分支?
- 如果通过
==
的左侧侧出现的差异(此处是引用表达式的属性与文字值)之间的差异来解释差异,您是否可以指出描述细节的C#规范. - 是否有可靠的经验法则可用于在编码时预测膨胀的 IL ,从而避免创建它?
- Is there any functional, semantic, or other substantial runtime difference between A and B? (We're only interested in correctness here, not performance)
- If they are not functionally equivalent, what are the runtime conditions that can expose an observable difference?
- If they are functional equivalents, what is B doing (that always ends up with the same result as A), and what triggered its spasm? Does B have branches that can never execute?
- If the difference is explained by the difference between what appears on the left side of
==
, (here, a property referencing expression versus a literal value), can you indicate a section of the C# spec that describes the details. - Is there a reliable rule-of-thumb that can be used to predict the bloated IL at coding-time, and thus avoid creating it?
奖金.每个堆栈的最终最终JITted x86
或AMD64
代码如何?
BONUS. How does the respective final JITted x86
or AMD64
code for each stack up?
基于注释反馈的其他注释.首先,提出了第三个变体,但它给出与 A 相同的IL(对于Debug
和Release
构建).顺便说一句,新版本的 C#似乎比 A 更漂亮:
[edit]
Additional notes based on feedback in the comments. First, a third variant was proposed, but it gives identical IL as A (for both Debug
and Release
builds). Sylistically, however, the C# for the new one does seem sleeker than A:
static void C(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
throw null;
}
这里也是每个功能的Release
IL.请注意,对于Release
IL, A / C 与 B 的不对称性仍然很明显,因此原始问题仍然存在.>
释放功能'A','C'的IL ...
Here also is the Release
IL for each function. Note that the asymmetry A/C vs. B is still evident with the Release
IL, so the original question still stands.
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
释放功能'B'的IL ...
[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
最后,提到了使用新的 C#7 语法的版本,该版本似乎产生了最干净的IL:
Lastly, a version using new C# 7 syntax was mentioned which seems to produce the cleanest IL of all:
static void D(ISupportInitialize x)
{
if (x is ISupportInitializeNotification y && y.IsInitialized)
throw null;
}
释放功能'D'的IL ...
[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
推荐答案
所以我很好奇答案,并研究了c#6规范(不知道c#7规范的托管位置).完全免责声明:我不保证我的回答是正确的,因为我没有编写c#规范/编译器,并且我对内部结构的理解受到限制.
So I was curious about the answer and took a look at the c# 6 specification (no clue where the c# 7 spec is hosted). Full disclaimer: I do not guarantee that my answer is correct, because I did not write the c# spec/compiler and my understanding of the internals is limited.
但我认为答案在于更好的功能成员.
Yet I think that the answer lies in the resultion of the overloadable ==
operator. The best applicable overload for ==
is determined by using the rules for better function members.
根据规格:
给出一个带有一组参数表达式{E1,E2, ...,En}和两个适用的带有参数的函数成员Mp和Mq 类型{P1,P2,...,Pn}和{Q1,Q2,...,Qn},Mp定义为 比Mq更好的功能成员
Given an argument list A with a set of argument expressions {E1, E2, ..., En} and two applicable function members Mp and Mq with parameter types {P1, P2, ..., Pn} and {Q1, Q2, ..., Qn}, Mp is defined to be a better function member than Mq if
对于每个参数,从Ex到Qx的隐式转换并不更好 比从Ex到Px的隐式转换,并且至少要转换一个 参数,从Ex到Px的转换要好于转换 从Ex到Qx.
for each argument, the implicit conversion from Ex to Qx is not better than the implicit conversion from Ex to Px, and for at least one argument, the conversion from Ex to Px is better than the conversion from Ex to Qx.
引起我注意的是参数列表{E1, E2, .., En}
.如果将Nullable<bool>
与bool
进行比较,则参数列表应该类似于{Nullable<bool> a, bool b}
,对于该参数列表,Nullable<bool>.Equals(object o)
方法似乎是最好的功能,因为它只需要从bool
进行一次隐式转换.到object
.
What caught my eye is the argument list {E1, E2, .., En}
. If you compare a Nullable<bool>
to a bool
the argument list should be something like {Nullable<bool> a, bool b}
and for that argument list the Nullable<bool>.Equals(object o)
method seems to be the best function, because it only takes one implicit conversion from bool
to object
.
但是,如果将参数列表的顺序恢复为{bool a, Nullable<bool> b}
,则Nullable<bool>.Equals(object o)
方法不再是最好的功能,因为现在您必须在第一个参数中将Nullable<bool>
转换为bool
在第二个参数中从bool
到object
.这就是为什么对于情况 A 选择了不同的重载,这似乎会导致更清晰的IL代码.
However if you revert the order of the argument list to {bool a, Nullable<bool> b}
the Nullable<bool>.Equals(object o)
method no longer is the best function, because now you would have to convert from Nullable<bool>
to bool
in the first argument and then from bool
to object
in the second argument. That's why for case A a different overload is selected which seems to result in cleaner IL code.
再次说明,这是我对好奇心的满足,并且似乎符合c#规范.但是我还没有弄清楚如何调试编译器以了解实际情况.
Again this is an explanation that satisfies my own curiosity and seems to be in line with the c# spec. But I have yet to figure out how to debug the compiler to see what's actually going on.
这篇关于带有Nullable< T>的'=='的参数顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!