Mirando el código de lenguaje intermedio hay una diferencia:
.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
}
Aquí está el código compilé para conseguir esto:
void Method1()
{
if (x == null) x = new X();
}
void Method2()
{
x = x ?? new X();
}
para estar seguro de que es más rápido que si el tiempo de ambos.
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
Conclusión:
- Son casi lo mismo en este caso donde el valor es inicialmente nulo.
- El método que utiliza una instrucción if es considerablemente más rápido que el operador nulo coalescente cuando x ya no es nulo.
La diferencia cuando x no es miradas nulos como que podría ser debido a que el operador coalescente nula asignar el valor de x de nuevo a x (stfld
en la IL), mientras que el cheque nulo salta por encima de la instrucción stfld
cuando x es no nulo.
Ambos son tan rápidos que debe tener un muy lazo cerrado para notar la diferencia. Solo debe hacer este tipo de optimizaciones de rendimiento si ha perfilado su código con sus datos. Diferentes situaciones, diferentes versiones de .NET, diferentes compiladores, etc. pueden producir resultados diferentes.
En caso de que alguien quiere saber cómo llegué estos resultados o reproducirlas, aquí está el código que he utilizado:
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();
}
}
de responsabilidad: No de referencia es perfecto, y esto bechmark es ahora de ser perfecto, sobre todo para mantener las cosas simples. He realizado numerosas pruebas diferentes, p. con los métodos en un orden diferente, con y sin "calentamiento" primero, en diferentes períodos de tiempo, etc. Obtengo aproximadamente los mismos resultados cada vez. No tenía nada que demostrar de una forma u otra, así que cualquier parcialidad que favorezca un método u otro es accidental.
Yo diría que el segundo sería más rápido porque el ?? El operador tiene un propósito específico, pero puede compilar esos dos fragmentos y comparar el IL generado y/o ejecutar una prueba para ver cuál es más rápido. – tenor
@ MasterMax1313 la diferencia de IL no es tan irrelevante que hayas votado negativamente ... todavía he actualizado mi respuesta –
Ten en cuenta que ninguno de los dos es seguro para subprocesos. Y uno generalmente espera que getters sea seguro para subprocesos. Recomiendo usar 'Lazy' si * realmente * necesita una inicialización lenta. Es un poco más lento pero evita varias dificultades. –
CodesInChaos