В сборке с отключенной оптимизацией (обычно отладочные сборки) вы получите следующие две последовательности инструкций IL:
IL_0000: nop IL_0000: nop
IL_0001: ldnull IL_0001: ldnull
IL_0002: ldftn x IL_0002: ldftn x
IL_0008: newobj Action<int>..ctor IL_0008: newobj Action<int>..ctor
IL_000D: stloc.0 // foo IL_000D: stloc.0 // foo
IL_000E: ldloc.0 // foo IL_000E: ldloc.0 // foo
IL_000F: ldnull IL_000F: brtrue.s IL_0013
IL_0010: cgt.un IL_0011: br.s IL_001C
IL_0012: stloc.1
IL_0013: ldloc.1
IL_0014: brfalse.s IL_001F
IL_0016: ldloc.0 // foo IL_0013: ldloc.0 // foo
IL_0017: ldc.i4.s 0A IL_0014: ldc.i4.s 0A
IL_0019: callvirt Action<int>.Invoke IL_0016: callvirt Action<int>.Invoke
IL_001E: nop IL_001B: nop
IL_001F: ret IL_001C: ret
Здесь есть небольшие различия в отношении инструкций ветвления, но давайте собирать с включенной оптимизацией (обычноВыпуск сборки):
IL_0000: ldnull IL_0000: ldnull
IL_0001: ldftn x IL_0001: ldftn x
IL_0007: newobj Action<int>..ctor IL_0007: newobj Action<int>..ctor
IL_000C: stloc.0 // foo IL_000C: dup
IL_000D: ldloc.0 // foo IL_000D: brtrue.s IL_0011
IL_000E: brfalse.s IL_0018 IL_000F: pop
IL_0010: ldloc.0 // foo IL_0010: ret
IL_0011: ldc.i4.s 0A IL_0011: ldc.i4.s 0A
IL_0013: callvirt Action<int>.Invoke IL_0013: callvirt Action<int>.Invoke
IL_0018: ret IL_0018: ret
Опять небольшая разница в инструкциях ветки.В частности, в примере, использующем оператор объединения нулей, будет помещен дубликат ссылки делегата действия в стеке, тогда как в примере с оператором if будет использоваться временная локальная переменная.JITter может поместить оба в регистр, однако, это не является окончательным, что он будет вести себя по-разному.
Давайте попробуем что-то другое:
public static void Action1(Action<int> foo)
{
if (foo != null)
foo(10);
}
public static void Action2(Action<int> foo)
{
foo?.Invoke(10);
}
Это компилируется (опять же, с оптимизациейвключено) на:
IL_0000: ldarg.0 IL_0000: ldarg.0
IL_0001: brfalse.s IL_000B IL_0001: brfalse.s IL_000B
IL_0003: ldarg.0 IL_0003: ldarg.0
IL_0004: ldc.i4.s 0A IL_0004: ldc.i4.s 0A
IL_0006: callvirt Action<int>.Invoke IL_0006: callvirt Action<int>.Invoke
IL_000B: ret IL_000B: ret
Точно такой же код .Таким образом, различия в вышеприведенных примерах отличались из-за других вещей, кроме оператора слияния нуля.
Теперь, чтобы ответить на ваш конкретный вопрос, будут ли отличия последовательности ветвей от вашего примера влиять на производительность? Единственный способ узнать это - на самом деле тестировать. Однако , я был бы очень удивлен, если это окажется чем-то, что вам нужно принять во внимание.Вместо этого я бы выбрал стиль кода в зависимости от того, что вам проще всего написать, прочитать и понять.