Czy jest już za późno, aby dodać kolejną odpowiedź?
Napisałem mnóstwo kodu LINQ-to-objects i twierdzę, że przynajmniej w tej domenie dobrze jest zrozumieć obie składnie, aby zastosować którykolwiek z nich, który upraszcza kod - który nie zawsze jest składnią kropkową.
Oczywiście zdarzają się sytuacje, w których składnia kropkowa JEST właściwą drogą - inni podali kilka takich przypadków; myślę jednak, że zmieniono rozumienie - jeśli źle zrobisz, otrzymałeś zły rap. Podam więc próbkę, w której, moim zdaniem, zrozumienie jest przydatne.
Oto rozwiązanie zagadki polegającej na zastępowaniu cyfr: (rozwiązanie napisane przy użyciu LINQPad, ale może być samodzielne w aplikacji na konsolę)
// 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);
... które wyjścia:
N = 1, O = 6, K = 4
Nieźle, logika płynie liniowo i widzimy, że powstaje jedno poprawne rozwiązanie. Ta łamigłówka jest łatwa do rozwiązania ręcznie: rozumowanie, że 3>> N
0 i O
> 4 * N implikuje 8> = O
> = 4. Oznacza to, że istnieje maksymalnie 10 przypadków do przetestowania ręcznie (2 dla N
- by -5 dla O
). Wystarczająco zbłądziłem - ta łamigłówka jest oferowana w celach ilustracyjnych LINQ.
Transformacje kompilatora
Kompilator robi wiele, aby przetłumaczyć to na równoważną składnię kropkową. Poza zwykłymi drugimi i kolejnymi from
klauzulami zamienianymi w SelectMany
wywołania , mamy let
klauzule, które stają się Select
wywołaniami z rzutami, które wykorzystują przezroczyste identyfikatory . Jak zamierzam pokazać, konieczność nazwania tych identyfikatorów w składni kropek odbiera czytelność tego podejścia.
Mam sposób na ujawnienie, co robi kompilator, tłumacząc ten kod na składnię kropkową. Jeśli usuniesz komentarz z dwóch wyżej wymienionych wierszy i uruchom go ponownie, otrzymasz następujące dane wyjściowe:
N = 1, O = 6, K = 4
drzewo wyrażeń rozwiązania 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))). Wybierz (<> h_ TransparentIdentifier1 => nowy <> 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))). Gdzie (<> 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 product / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Wybierz (<> h_ TransparentIdentifier3 => nowy <> 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))
Umieszczenie każdego operatora LINQ w nowym wierszu, tłumaczenie „niewymownych” identyfikatorów na te, które możemy „wypowiedzieć”, zmiana typów anonimowych na ich znaną formę i zmiana języka AndAlso
lingwistycznego drzewa wyrażeń w celu &&
ujawnienia transformacji, które kompilator robi, aby osiągnąć równoważny w składni kropkowej:
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);
}
Co, jeśli uruchomisz, możesz sprawdzić, czy to ponownie generuje:
N = 1, O = 6, K = 4
... ale czy kiedykolwiek napisałbyś taki kod?
Założę się, że odpowiedź brzmi NONBHN (nie tylko nie, ale piekło nie!) - ponieważ jest to po prostu zbyt skomplikowane. Jasne, że możesz wymyślić bardziej znaczące nazwy identyfikatorów niż „temp0” .. „temp3”, ale chodzi o to, że nie dodają niczego do kodu - nie poprawiają kodu, nie poprawiają sprawi, że kod będzie lepiej czytany, tylko brzydko go poprawiają, a jeśli robisz to ręcznie, bez wątpienia zepsułbyś go raz lub trzy, zanim zrobisz to poprawnie. Ponadto granie w „grę z imionami” jest wystarczająco trudne do uzyskania znaczących identyfikatorów, dlatego z zadowoleniem przyjmuję odejście od gry z imionami, które kompilator zapewnia mi w zrozumieniu zapytań.
Próbka ta zagadka może nie być w świecie rzeczywistym na tyle, aby podjąć poważnie; istnieją jednak inne scenariusze, w których świecą pojęcia zapytań:
- Złożoność
Join
i GroupJoin
: zakres zmiennych zasięgu w join
klauzulach rozumienia zapytań zamienia błędy, które w przeciwnym razie mogłyby się kompilować w składni kropkowej, w błędy czasu kompilacji w składni rozumienia.
- Za każdym razem, gdy kompilator wprowadza przejrzysty identyfikator w transformacji rozumienia, zrozumienie staje się opłacalne. Obejmuje to użycie któregokolwiek z poniższych: wielu
from
klauzul oraz join
& join..into
i let
klauzul.
Znam więcej niż jeden warsztat inżynieryjny w moim rodzinnym mieście, który zabronił składni rozumienia. Myślę, że szkoda, ponieważ składnia rozumienia jest tylko narzędziem i jest w tym przydatna. Myślę, że to tak, jakby powiedzieć: „Są rzeczy, które można zrobić za pomocą śrubokręta, których nie można zrobić za pomocą dłuta. Ponieważ można użyć śrubokręta jako dłuta, dłuta są odtąd zakazane na mocy dekretu króla”.