LINQ Coding Guidelines #2–Kein Mischen der Syntax

14. November 2014

Nach der Strafandrohung vom letzten Mal haben die folgenden Guidelines eher den Charakter von – mehr oder weniger strengen – Empfehlungen.

Empfehlung:
Das Mischen von Query-Syntax und Method-Syntax sollte vermieden werden.

 

Um’s nochmal deutlich zu machen: Wir reden von Query-Syntax…

   1: int[] numbers = { 5, 10, 8, 3, 6, 12};

   2: var numQuery1 = from num in numbers

   3:     where num % 2 == 0

   4:     orderby num

   5:     select num;

versus Method-Syntax…

   1: int[] numbers = { 5, 10, 8, 3, 6, 12};

   2: var numQuery2 = numbers

   3:     .Where(num => num % 2 == 0)

   4:     .OrderBy(n => n);

In diesem einfachen Beispiel macht das keinen großen Unterschied. Tatsächlich neigen viele Entwickler zur Query-Syntax, weil sie näher an SQL liegt und nicht zuletzt, weil Microsoft sie in seinen Beispielen bevorzugt.

Dummerweise ist die Query-Syntax aber auf einen festen Satz an Schlüsselwörtern beschränkt; selbst Aggregatfunktionen – obwohl Teil von SQL – sind darin schon nicht mehr enthalten. Und auch bei vorhandenen Schlüsselwörtern erlauben diese nicht alle Optionen, die mit der Method-Syntax möglich sind, etwa ein OrderBy mit Comparer. Von zusätzliche Funktionalitäten aus eigenem Code oder Bibliotheken ganz zu schweigen.

In diesen Fällen muss man auf die Method-Syntax ausweichen. Leider legt Microsoft hier mit Vermischung der beiden Varianten vor (1, 2):

   1: string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; 

   2: var reversedIDigits = ( 

   3:     from d in digits 

   4:     where d[1] == 'i' 

   5:     select d) 

   6:     .Reverse(); 

   1: List<Customer> customers = GetCustomerList(); 

   2: var first3WAOrders = ( 

   3:     from c in customers 

   4:     from o in c.Orders 

   5:     where c.Region == "WA" 

   6:     select new { c.CustomerID, o.OrderID, o.OrderDate }) 

   7:     .Take(3); 

Und so weiter.

Der Raytracer als pathologisches Beispiel mischt das ebenfalls, was nur bei genauem Hinsehen erkennbar ist.

Hier ist ein Beispiel aus produktivem Code:

   1: static string GetOSVersion()

   2: {

   3:     var name = (from x in new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem").Get().OfType<ManagementObject>() select x.GetPropertyValue("Caption")).First();

   4:     return name != null ? name.ToString() : "Unknown";

   5: }

Den LINQ-Ausdruck in einer Zeile zu packen macht’s nicht eben besser lesbar, aber Umbrüche helfen auch nicht wirklich:

   1: static string GetOSVersion()

   2: {

   3:     var name = (from x

   4:                     in new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem")

   5:                     .Get()

   6:                     .OfType<ManagementObject>()

   7:                 select x.GetPropertyValue("Caption"))

   8:                     .First();

   9:     return name != null ? name.ToString() : "Unknown";

  10: }

 

Problem dabei: Durch die Mischung fällt es beim Lesen schwerer, die Logik zu erfassen. Gleiche Dinge werden unterschiedlich dargestellt, konzeptionelle Unterschiede und Gemeinsamkeiten werden verwischt.

Wenn man den ganzen Ausdruck hingegen konsequent in Method-Syntax hinschreibt, wird die Logik auf einmal sehr offensichtlich und einfach nachvollziehbar. Das letzte Beispiel in Method-Syntax:

   1: static string GetOSVersion2()

   2: {

   3:     var name = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem")

   4:         .Get()

   5:         .OfType<ManagementObject>()

   6:         .Select(x => x.GetPropertyValue("Caption"))

   7:         .First();

   8:     return name != null ? name.ToString() : "Unknown";

   9: }

Wie ich finde ein ausreichender Grund, um – zumindest in den beschriebenen Fällen – auf eine Mischung – und damit letztlich auf die Query-Syntax – zu verzichten. Womit die Empfehlung begründet ist.

 

Um aber einen Schritt weiter zu gehen…

Einfache Ausdrücke kann man nach wie vor mit der Query-Syntax schreiben – ohne die Empfehlung zu verletzen. Allerdings habe ich die Erfahrung gemacht, dass einfache Ausdrücke nicht immer so einfach bleiben. Sie später umzuschreiben ist mindestens nervig.

Ich persönlich – und ich bin da nicht der Einzige – habe mir daher die Query-Syntax mittlerweile völlig abgewöhnt und nutze grundsätzlich die Method-Syntax.

Das mag Geschmackssache sein; wer das in seine Coding Guidelines aufnehmen will, kann zumindest Konsistenz über verschiedene Ausdrücke hinweg als Argument anführen.