Ответ 1
Глядя на код промежуточного языка, есть разница:
.method private hidebysig instance void Method1() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldfld class X Program::x
L_0006: brtrue.s L_0013
L_0008: ldarg.0
L_0009: newobj instance void X::.ctor()
L_000e: stfld class X Program::x
L_0013: ret
}
.method private hidebysig instance void Method2() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldarg.0
L_0002: ldfld class X Program::x
L_0007: dup
L_0008: brtrue.s L_0010
L_000a: pop
L_000b: newobj instance void X::.ctor()
L_0010: stfld class X Program::x
L_0015: ret
}
Вот код, который я скомпилировал, чтобы получить следующее:
void Method1()
{
if (x == null) x = new X();
}
void Method2()
{
x = x ?? new X();
}
Чтобы быть уверенным, что это быстрее, вам нужно время обойти.
Method Initial condition Iterations per second --------------------------------------------------- NullCheck x is null 33 million Coalesce x is null 33 million NullCheck x is not null 40 million Coalesce x is not null 33 million
Вывод:
- Они примерно одинаковы в этом случае, когда значение изначально равно null.
- Метод, использующий оператор if, значительно быстрее, чем оператор нулевой коалесценции, когда x уже не null.
Разница, когда x не равна null, выглядит так, как это может быть из-за оператора нулевой коалесценции, присваивающего значение x обратно x (stfld
в IL), тогда как нулевая проверка перескакивает по команде stfld
, когда x не является нулевым.
Оба настолько быстрей, что у вас должен быть очень узкий цикл, чтобы заметить разницу. Вы должны делать только такие оптимизации производительности, если вы профилировали свой код своими данными. Различные ситуации, разные версии .NET, разные компиляторы и т.д. Могут давать разные результаты.
Если кто-то хочет знать, как я получил эти результаты или воспроизвел их, вот код, который я использовал:
using System;
class X { }
class Program
{
private X x;
private X xNull = null;
private X xNotNull = new X();
private void Method1Null()
{
x = xNull;
if (x == null) x = xNotNull;
}
private void Method2Null()
{
x = xNull;
x = x ?? xNotNull;
}
private void Method1NotNull()
{
x = xNotNull;
if (x == null) x = xNotNull;
}
private void Method2NotNull()
{
x = xNotNull;
x = x ?? xNotNull;
}
private const int repetitions = 1000000000;
private void Time(Action action)
{
DateTime start = DateTime.UtcNow;
for (int i = 0; i < repetitions; ++i)
{
action();
}
DateTime end = DateTime.UtcNow;
Console.WriteLine(repetitions / (end - start).TotalSeconds);
}
private void Run()
{
Time(() => { Method1Null(); });
Time(() => { Method2Null(); });
Time(() => { Method1NotNull(); });
Time(() => { Method2NotNull(); });
Console.WriteLine("Finished");
Console.ReadLine();
}
private static void Main()
{
new Program().Run();
}
}
Отказ от ответственности: нет идеального теста, и этот bechmark далек от совершенства, в основном, чтобы все было просто. Я провела множество различных тестов, например. с методами в другом порядке, с и без "разогрева" сначала, в разные промежутки времени и т.д. Я получаю примерно одинаковые результаты каждый раз. У меня не было ничего, чтобы доказать, так или иначе, поэтому любое смещение в пользу одного метода или другого случая является случайным.