Следующие две функции C#
отличаются только заменой порядка аргументов влево/вправо оператору equals, ==
. (Тип IsInitialized
- bool
). Использование С# 7.1 и .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:
- 36 байт дольше (код IL);
- вызывает дополнительные функции, включая
newobj
иinitobj
; - объявляет четырех локальных жителей по сравнению с одним.
IL для функции '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 для функции '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
- Есть ли функциональная, семантическая или другая существенная разница во времени между A и B? (Нас интересует только правильность, а не производительность).
- Если они не являются функционально эквивалентными, каковы условия выполнения, которые могут выявить наблюдаемую разницу?
- Если они являются функциональными эквивалентами, что делает B (что всегда заканчивается тем же результатом, что и A), и что вызвало его спазм? Имеет ли B ветки, которые никогда не могут выполняться?
- Если разница объясняется разницей между тем, что появляется на стороне слева
==
(здесь выражение привязки свойства к буквенному значению), вы можете указать раздел С# spec, который описывает детали. - Есть ли надежное правило большого пальца, которое можно использовать для прогнозирования раздутого IL во время кодирования и, таким образом, избежать его создания?
БОНУС. Каким образом соответствующий конечный код JITted x86
или AMD64
для каждого стека?
[править]
Дополнительные примечания, основанные на отзывах в комментариях. Сначала был предложен третий вариант, но он дает идентичный IL как A (для обоих Debug
и Release
строит). Однако силистически С# для нового кажется более гладким, чем A:
static void C(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
throw null;
}
Здесь также есть Release
IL для каждой функции. Обратите внимание, что асимметрия A/ C и B по-прежнему очевидна с IL Release
, поэтому исходный вопрос все еще стоит.
Выпуск IL для функций "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
Отпустить IL для функции '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
Наконец, была упомянута версия с использованием нового синтаксиса С# 7, который, по-видимому, производит самый чистый IL из всех:
static void D(ISupportInitialize x)
{
if (x is ISupportInitializeNotification y && y.IsInitialized)
throw null;
}
Выпуск IL для функции '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