大量使用 . 有任何开销吗? field/property 比较的运算符?
Is there any overhead in massively using .? operators for field/property comparisons?
如果我们有 2 个对象并且我们已经重载了一个相等运算符来比较每个对象,我们是否应该厌倦使用 ?运算符(例如 firstObject?.MyProperty == secondObject?.MyProperty
)如果我们最终在 20-30 个属性上使用它?
基本上对于属性比较多的大对象,做下面的比较是不是比较好
(firstObject == null && secondObject == null || !(firstObject == null ^ secondObject == null))
&& (<property comparisons without ?>)
或者干脆
<property comparisons with ?>
谢谢。
在本质上它们是 不同的。 ?.
更具可读性并且 更快 (似乎)。
证明
首先,考虑这个样本(目标 .NET Framework 4.7.2
):
class Program
{
class Foo
{
public Foo Bar { get; set; }
}
static void Main(string[] args)
{
var foo = new Foo();
var t0 = DateTime.UtcNow.Ticks;
for (int i = 0; i < 1000000000; i++)
{
UsingElvisOperator(foo);
// UsingIfsAndButs(foo);
}
var t1 = DateTime.UtcNow.Ticks;
Console.WriteLine($"Elapsed time: {(t1 - t0) / 10000} ms");
}
private static void UsingIfsAndButs(Foo foo)
{
if (foo?.Bar?.Bar?.Bar != null)
{
Console.WriteLine("OK");
}
}
private static void UsingElvisOperator(Foo foo)
{
if (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
{
Console.WriteLine("OK");
}
}
}
在 Any CPU
平台的 Release
模式下编译它。现在让我们使用 ildasm.
反编译生成的 .exe
猫王运算符的说明:
.method private hidebysig static void UsingElvisOperator(class OperatorTest.Program/Foo foo) cil managed
{
// Code size 53 (0x35)
.maxstack 8
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0034
IL_0003: ldarg.0
IL_0004: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0009: brfalse.s IL_0034
IL_000b: ldarg.0
IL_000c: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0011: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0016: brfalse.s IL_0034
IL_0018: ldarg.0
IL_0019: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_001e: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0023: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0028: brfalse.s IL_0034
IL_002a: ldstr "OK"
IL_002f: call void [mscorlib]System.Console::WriteLine(string)
IL_0034: ret
} // end of method Program::UsingElvisOperator
以及 if-checks 的说明:
.method private hidebysig static void UsingIfsAndButs(class OperatorTest.Program/Foo foo) cil managed
{
// Code size 49 (0x31)
.maxstack 8
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldnull
IL_0004: br.s IL_0024
IL_0006: ldarg.0
IL_0007: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_000c: dup
IL_000d: brtrue.s IL_0013
IL_000f: pop
IL_0010: ldnull
IL_0011: br.s IL_0024
IL_0013: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0018: dup
IL_0019: brtrue.s IL_001f
IL_001b: pop
IL_001c: ldnull
IL_001d: br.s IL_0024
IL_001f: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0024: brfalse.s IL_0030
IL_0026: ldstr "OK"
IL_002b: call void [mscorlib]System.Console::WriteLine(string)
IL_0030: ret
} // end of method Program::UsingIfsAndButs
很明显,生成的指令是不同的。
性能比较
运行 两种方法 10 亿次 次,在我的机器上,得到以下结果:
UsingElvisOperator: 1704 ms
UsingIfsAndButs: 2815 ms
只想分享我的基准测试总结,它检查 null 和非 null 测试对象,这显示了“ifs”的速度优势:
// * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
Intel Core i5-7300U CPU 2.60GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.301
[Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
Job=NewJob IterationCount=2 LaunchCount=2
RunStrategy=Throughput WarmupCount=1
| Method | Mean | Error | StdDev |
|
------------------- |---------:|----------:|----------:|
| UsingIfsAndButs | 1.518 ns | 0.4293 ns | 0.0664 ns |
| UsingElvisOperator | 2.144 ns | 0.2124 ns | 0.0329 ns |
和代码:
[SimpleJob(RunStrategy.Throughput, launchCount: 2, warmupCount: 1, targetCount: 2, id: "NewJob")]
public class ElvisBenchmarks
{
Foo foo;
Foo foo_null;
Boolean isNull;
public ElvisBenchmarks()
{
foo = new Foo();
}
[Benchmark]
public void UsingIfsAndButs()
{
isNull = (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
? true : false;
isNull = (foo_null != null && foo_null.Bar != null && foo_null.Bar.Bar != null && foo_null.Bar.Bar.Bar != null)
? true : false;
}
[Benchmark]
public void UsingElvisOperator()
{
isNull = (foo?.Bar?.Bar?.Bar != null) ? true : false;
isNull = (foo_null?.Bar?.Bar?.Bar != null) ? true : false;
}
}
class Foo
{
public Foo Bar { get; set; }
}
如果我们有 2 个对象并且我们已经重载了一个相等运算符来比较每个对象,我们是否应该厌倦使用 ?运算符(例如 firstObject?.MyProperty == secondObject?.MyProperty
)如果我们最终在 20-30 个属性上使用它?
基本上对于属性比较多的大对象,做下面的比较是不是比较好
(firstObject == null && secondObject == null || !(firstObject == null ^ secondObject == null))
&& (<property comparisons without ?>)
或者干脆
<property comparisons with ?>
谢谢。
在本质上它们是 不同的。 ?.
更具可读性并且 更快 (似乎)。
证明
首先,考虑这个样本(目标 .NET Framework 4.7.2
):
class Program { class Foo { public Foo Bar { get; set; } } static void Main(string[] args) { var foo = new Foo(); var t0 = DateTime.UtcNow.Ticks; for (int i = 0; i < 1000000000; i++) { UsingElvisOperator(foo); // UsingIfsAndButs(foo); } var t1 = DateTime.UtcNow.Ticks; Console.WriteLine($"Elapsed time: {(t1 - t0) / 10000} ms"); } private static void UsingIfsAndButs(Foo foo) { if (foo?.Bar?.Bar?.Bar != null) { Console.WriteLine("OK"); } } private static void UsingElvisOperator(Foo foo) { if (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null) { Console.WriteLine("OK"); } } }
在 Any CPU
平台的 Release
模式下编译它。现在让我们使用 ildasm.
.exe
猫王运算符的说明:
.method private hidebysig static void UsingElvisOperator(class OperatorTest.Program/Foo foo) cil managed
{
// Code size 53 (0x35)
.maxstack 8
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0034
IL_0003: ldarg.0
IL_0004: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0009: brfalse.s IL_0034
IL_000b: ldarg.0
IL_000c: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0011: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0016: brfalse.s IL_0034
IL_0018: ldarg.0
IL_0019: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_001e: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0023: callvirt instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0028: brfalse.s IL_0034
IL_002a: ldstr "OK"
IL_002f: call void [mscorlib]System.Console::WriteLine(string)
IL_0034: ret
} // end of method Program::UsingElvisOperator
以及 if-checks 的说明:
.method private hidebysig static void UsingIfsAndButs(class OperatorTest.Program/Foo foo) cil managed
{
// Code size 49 (0x31)
.maxstack 8
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldnull
IL_0004: br.s IL_0024
IL_0006: ldarg.0
IL_0007: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_000c: dup
IL_000d: brtrue.s IL_0013
IL_000f: pop
IL_0010: ldnull
IL_0011: br.s IL_0024
IL_0013: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0018: dup
IL_0019: brtrue.s IL_001f
IL_001b: pop
IL_001c: ldnull
IL_001d: br.s IL_0024
IL_001f: call instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
IL_0024: brfalse.s IL_0030
IL_0026: ldstr "OK"
IL_002b: call void [mscorlib]System.Console::WriteLine(string)
IL_0030: ret
} // end of method Program::UsingIfsAndButs
很明显,生成的指令是不同的。
性能比较
运行 两种方法 10 亿次 次,在我的机器上,得到以下结果:
UsingElvisOperator: 1704 ms
UsingIfsAndButs: 2815 ms
只想分享我的基准测试总结,它检查 null 和非 null 测试对象,这显示了“ifs”的速度优势:
// * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
Intel Core i5-7300U CPU 2.60GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.301
[Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT
Job=NewJob IterationCount=2 LaunchCount=2
RunStrategy=Throughput WarmupCount=1
| Method | Mean | Error | StdDev |
|
------------------- |---------:|----------:|----------:|
| UsingIfsAndButs | 1.518 ns | 0.4293 ns | 0.0664 ns |
| UsingElvisOperator | 2.144 ns | 0.2124 ns | 0.0329 ns |
和代码:
[SimpleJob(RunStrategy.Throughput, launchCount: 2, warmupCount: 1, targetCount: 2, id: "NewJob")]
public class ElvisBenchmarks
{
Foo foo;
Foo foo_null;
Boolean isNull;
public ElvisBenchmarks()
{
foo = new Foo();
}
[Benchmark]
public void UsingIfsAndButs()
{
isNull = (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
? true : false;
isNull = (foo_null != null && foo_null.Bar != null && foo_null.Bar.Bar != null && foo_null.Bar.Bar.Bar != null)
? true : false;
}
[Benchmark]
public void UsingElvisOperator()
{
isNull = (foo?.Bar?.Bar?.Bar != null) ? true : false;
isNull = (foo_null?.Bar?.Bar?.Bar != null) ? true : false;
}
}
class Foo
{
public Foo Bar { get; set; }
}