Si nous avons 2 objets et que nous avons surchargé un opérateur d'égalité pour comparer chacun d'eux, devrions-nous nous lasser de l'utilisation du? opérateur (par exemple firstObject? .MyProperty == secondObject? .MyProperty
) si nous finissons par utiliser cela sur, disons 20-30 propriétés?
Fondamentalement, pour les grands objets avec beaucoup de propriétés, est-ce mieux pour faire la comparaison suivante
(firstObject == null && secondObject == null || !(firstObject == null ^ secondObject == null)) && (<property comparisons without ?>)
ou simplement
Merci vous.
3 Réponses :
Non, sous le capot, ce sont les mêmes. Le ?.
est plus lisible et fait exactement la même chose dans les coulisses.
Techniquement, ils pourraient être légèrement différents à cause de l'évaluation des courts-circuits (s'ils sont tous les deux nuls, cela peut ignorer tout le reste de l'alternative) mais vous regardez les microsecondes (si c'est le cas). Vous perdriez plus du temps nécessaire pour charger du code plus long dans la RAM.
Si vous voulez vous le prouver, vous pouvez faire ce que @Caius Jard a suggéré et le mettre dans une boucle qui effectue des millions / milliards de vérifications d'égalité.
Sous le capot, ils sont différents . Le ?.
est plus lisible et plus rapide (semble-t-il).
Tout d'abord, considérons cet exemple (ciblant .NET Framework 4.7.2
):
UsingElvisOperator: 1704 ms UsingIfsAndButs: 2815 ms
Compilez-le en mode Release
pour la plate-forme Any CPU
. Décompilons maintenant le .exe
produit en utilisant ildasm .
Les instructions pour l ' opérateur Elvis :
.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
Et les instructions pour les if-checks
.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
De toute évidence, les instructions générées sont différentes.
Lancer les deux méthodes un milliard de fois, sur ma machine, me donne les résultats suivants:
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"); } } }
Il est important de noter cependant que vous n'avez pas évalué la question des opérations. Oops. de toute façon, plus un pour l'effort, et j'utiliserais toujours le conditionnel nul moi-même, peu importe
@TheGeneral Vous avez raison. Je ne comprends pas parfaitement le PO, alors j'ai fait une interprétation créative.
je veux juste partager mon résumé de benchmarking qui vérifie l'objet de test nul et non nul, ce qui montre un avantage en vitesse pour les "ifs":
[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; } }
et le code: p>
// * 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 |
Intéressant ... des théories sur pourquoi cela se produit? Si vous testez non pas les Foo imbriqués mais les propriétés de foo, je pense que le résultat sera le contraire.
Lorsque vous l'avez chargé dans une boucle qui a effectué un milliard de vérifications d'égalité, quels ont été les résultats du test de performance?
Si vous avez beaucoup de champs, il est plus propre de vérifier d'abord l'objet entier pour
null
et d'éviter de répéter le codeIl n'y a pas mieux, parce que vous n'avez donné aucun domaine dans lequel quelque chose peut être meilleur. Le conditionnel nul générera plus d'IL, mais semble plus net. Pourtant, les deux font la même chose. En fin de compte, toute différence de performances pourrait bien être optimisée
@CaiusJard avec? comparaisons d'opérateurs J'ai obtenu 00: 03: 57.3792339, sans? c'est 00: 03: 48.5972988 (exécuté sur Release, sans débogage). Je ne sais pas si la différence est réelle ou non, mais cela ne semble pas avoir beaucoup d'impact.
Je suppose que cela revient à une question de "combien d'heures / jours / années faudra-t-il avant que le système de production n'ait fait un milliard de comparaisons légitimes, et les 9 secondes économisées valent-elles la peine par rapport à la propreté du code. . ". Si prod fait un milliard par rapport à une heure, cela pourrait représenter une économie raisonnable; s'il y a un an de fonctionnement avant que le compte de comparaison ne soit atteint, cela ne vaut pas la laideur (IMHO)
@TheGeneral Null-conditionnel (alias "opérateur Elvis") génère moins IL.