Ist es jemals zu spät, eine weitere Antwort hinzuzufügen?
Ich habe eine Menge LINQ-to-Objects-Code geschrieben und behaupte, dass es zumindest in diesem Bereich gut ist, beide Syntaxen zu verstehen, um den einfacheren Code zu verwenden - was nicht immer Punktsyntax ist.
Natürlich gibt es Zeiten , in denen Punktsyntax IS der Weg zu gehen - andere haben mehrere dieser Fälle vorgesehen ist ; Ich denke jedoch, dass das Verständnis zu kurz gekommen ist - wenn man so will, mit einem schlechten Ruf. Ich werde daher ein Beispiel bereitstellen, in dem ich Verständnis für nützlich halte.
Hier ist eine Lösung für ein Ziffernersetzungspuzzle: (Lösung, die mit LINQPad geschrieben wurde, aber in einer Konsolen-App eigenständig sein kann)
// NO
// NO
// NO
//+NO
//===
// OK
var solutions =
from O in Enumerable.Range(1, 8) // 1-9
//.AsQueryable()
from N in Enumerable.Range(1, 8) // 1-9
where O != N
let NO = 10 * N + O
let product = 4 * NO
where product < 100
let K = product % 10
where K != O && K != N && product / 10 == O
select new { N, O, K };
foreach(var i in solutions)
{
Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}
//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);
... welche Ausgänge:
N = 1, O = 6, K = 4
Nicht schlecht, die Logik fließt linear und wir können sehen, dass es eine einzige richtige Lösung gibt. Dieses Rätsel lässt sich leicht von Hand lösen: Die Schlussfolgerung 3>> N
0 und O
> 4 * N impliziert 8> = O
> = 4. Das bedeutet, dass maximal 10 Fälle von Hand getestet werden können (2 für N
-by- 5 für O
). Ich habe mich genug verirrt - dieses Rätsel wird zur Veranschaulichung von LINQ angeboten.
Compiler-Transformationen
Der Compiler unternimmt viel, um dies in äquivalente Punktsyntax umzusetzen. Abgesehen davon, dass die üblichen zweiten und nachfolgenden from
Klauseln in SelectMany
Aufrufe umgewandelt werden , gibt es let
Klauseln, die zu Select
Aufrufen mit Projektionen werden und beide transparente Bezeichner verwenden . Wie ich gleich zeigen werde, beeinträchtigt das Benennen dieser Bezeichner in der Punktsyntax die Lesbarkeit dieses Ansatzes.
Ich habe einen Trick, um herauszufinden, was der Compiler bei der Übersetzung dieses Codes in Punktsyntax tut. Wenn Sie die beiden obigen Kommentarzeilen auskommentieren und erneut ausführen, erhalten Sie die folgende Ausgabe:
N = 1, O = 6, K = 4
Lösungsausdrucksbaum System.Linq.Enumerable + d_b8.SelectMany (O => Range (1, 8), (O, N) => new <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType1
2 (<> h_ TransparentIdentifier0 = <> h _TransparentIdentifier0, NO = ((10 * <> h_ TransparentIdentifier0.N) + <> h _TransparentIdentifier0.O)). Wählen Sie (<> h_ TransparentIdentifier1 => new <> f _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType3
2 (<> h_ TransparentIdentifier2 = <> h _TransparentIdentifier2, K = ( <> h_ TransparentIdentifier2.product% 10))). Where (<> h _TransparentIdentifier3 => (((<> h_ TransparentIdentifier3.K! = <> h _TransparentIdentifier3. <> h_ TransparentIdentifier2. <>h _TransparentIdentifier1. <> h_TransparentIdentifier0.O) AndAlso (<> h _TransparentIdentifier3.K! = <> H_ TransparentIdentifier3. <> H _TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.N)) AndAlso ((<> h_ TransparentIdentifier3. <> H _TransparentIdentifier2. product / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Select (<> h_ TransparentIdentifier3 => new <> f _AnonymousType4`3 (N = < > h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.N,O = <> h_ TransparentIdentifier3. <> H_TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))
Setzen Sie jeden LINQ-Operator in eine neue Zeile, übersetzen Sie die "unaussprechlichen" Bezeichner in diejenigen, die wir "sprechen" können, ändern Sie die anonymen Typen in ihre vertraute Form und ändern Sie den AndAlso
Ausdrucksbaumjargon, um &&
die Transformationen anzuzeigen, die der Compiler ausführt, um zu einem Äquivalent zu gelangen in Punktsyntax:
var solutions =
Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
.SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
.Where(temp0 => temp0.O != temp0.N) // where O != N
.Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
.Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
.Where(temp2 => temp2.product < 100) // where product < 100
.Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
.Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
// where K != O && K != N && product / 10 == O
.Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
// select new { N, O, K };
foreach(var i in solutions)
{
Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}
Was, wenn Sie ausführen, können Sie überprüfen, dass es wieder ausgibt:
N = 1, O = 6, K = 4
... aber würdest du jemals so einen Code schreiben?
Ich würde wetten, dass die Antwort NONBHN ist (nicht nur Nein, sondern verdammt Nein!) - weil es einfach zu komplex ist. Sicher können Sie sich aussagekräftigere Bezeichnernamen als "temp0" .. "temp3" einfallen lassen, aber der Punkt ist, dass sie dem Code nichts hinzufügen - sie sorgen nicht dafür, dass der Code besser funktioniert, sie tun es nicht Verbessern Sie die Lesbarkeit des Codes, da der Code nur hässlich ist. Wenn Sie dies von Hand tun würden, würden Sie ihn ohne Zweifel ein oder drei Mal durcheinander bringen, bevor Sie es richtig machen. Außerdem ist das Spielen des "Namensspiels" für aussagekräftige Bezeichner schwierig genug, sodass ich die Unterbrechung des Namensspiels begrüße, die mir der Compiler beim Verständnis von Abfragen bereitstellt.
Dieses Rätselbeispiel ist möglicherweise nicht real genug, um von Ihnen ernst genommen zu werden. Es gibt jedoch auch andere Szenarien, in denen das Abfrageverständnis glänzt:
- Die Komplexität von
Join
und GroupJoin
: das Scoping von Bereichsvariablen in Abfrageverständnisklauseln join
wandelt Fehler, die ansonsten in Punktsyntax kompiliert würden, in Fehler zur Kompilierungszeit in der Verständnissyntax um.
- Jedes Mal, wenn der Compiler einen transparenten Bezeichner in die Verstehenstransformation einführt, werden Verstehen lohnenswert. Dies schließt die Verwendung einer der folgenden
from
Klauseln ein : multiple Klauseln, join
& join..into
Klauseln und let
Klauseln.
Ich kenne mehr als eine Werkstatt in meiner Heimatstadt, in der die Syntax des Verstehens verboten ist . Ich denke, das ist schade, da die Syntax des Verständnisses nur ein Werkzeug und noch dazu ein nützliches ist. Ich denke, es ist sehr ähnlich zu sagen: "Es gibt Dinge, die Sie mit einem Schraubenzieher tun können, die Sie nicht mit einem Meißel tun können. Da Sie einen Schraubenzieher als Meißel verwenden können, werden Meißel von nun an auf Anordnung des Königs verboten."