È mai troppo tardi per aggiungere un'altra risposta?
Ho scritto un sacco di codice LINQ-to-object e sostengo che almeno in quel dominio è bene capire entrambe le sintassi per usare qualunque sia il codice più semplice, che non è sempre una sintassi punto.
Naturalmente ci sono momenti in cui la sintassi del punto È la strada da percorrere - altri hanno fornito molti di questi casi; tuttavia, penso che le comprensioni siano state modificate a breve - dato un brutto colpo, se vuoi. Quindi fornirò un esempio in cui credo che le comprensioni siano utili.
Ecco una soluzione a un puzzle di sostituzione delle cifre: (soluzione scritta usando LINQPad, ma può essere autonoma in un'app console)
// 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);
... che genera:
N = 1, O = 6, K = 4
Non male, la logica scorre in modo lineare e possiamo vedere che viene fuori con un'unica soluzione corretta. Questo puzzle è abbastanza facile da risolvere a mano: ragionando che 3>> N
0 e O
> 4 * N implica 8> = O
> = 4. Ciò significa che ci sono un massimo di 10 casi da testare a mano (2 perN
-by- 5 per O
). Ho smarrito abbastanza: questo puzzle è offerto a scopo illustrativo LINQ.
Trasformazioni del compilatore
C'è molto che il compilatore fa per tradurre questo in sintassi dot equivalente. Oltre alle solite seconde e successive from
clausole che vengono trasformate in SelectMany
chiamate, abbiamo let
clausole che diventano Select
chiamate con proiezioni, entrambe le quali usano identificatori trasparenti . Come sto per mostrare, dover nominare questi identificatori nella sintassi del punto toglie la leggibilità di quell'approccio.
Ho un trucco per esporre ciò che fa il compilatore nel tradurre questo codice in sintassi punto. Se si decommenta le due righe commentate sopra e lo si esegue di nuovo, si otterrà il seguente output:
N = 1, O = 6, K = 4
albero delle espressioni della soluzione 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))). Selezionare (<> 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 h _TransparentIdentifier3.K! = <> h_TransparentIdentifier3. <> H _TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.N)) AndAlso ((<> h_ TransparentIdentifier3. <> H _TransparentIdentifier > <> h ) h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Selezionare (<> h_ TransparentIdentifier3 => nuova <> f _AnonymousType4`3 (N = <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2 . <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.N, O = <> h_ TransparentIdentifier3. <_TransparentIdentifier2. <> H_TransparentIdentifier1. <> H _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))
Mettendo ogni operatore LINQ su una nuova riga, traducendo gli identificatori "indicibili" in quelli che possiamo "parlare", cambiando i tipi anonimi nella loro forma familiare e cambiando il AndAlso
gergo dell'albero delle espressioni per &&
esporre le trasformazioni che il compilatore fa per arrivare ad un equivalente in sintassi punto:
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);
}
Che se esegui puoi verificare che restituisca di nuovo:
N = 1, O = 6, K = 4
... ma scriveresti mai un codice come questo?
Scommetto che la risposta è NONBHN (non solo no, ma l'inferno no!) - perché è troppo complessa. Sicuramente puoi trovare alcuni nomi di identificatori più significativi di "temp0" .. "temp3", ma il punto è che non aggiungono nulla al codice - non fanno funzionare meglio il codice, non lo fanno rendere il codice migliore, rendono il brutto codice e se lo facessi a mano, senza dubbio lo sbaglieresti una o tre volte prima di farlo bene. Inoltre, giocare a "il gioco del nome" è abbastanza difficile per identificatori significativi, quindi accolgo con favore l'interruzione del gioco del nome che il compilatore mi fornisce nella comprensione delle query.
Questo esempio di puzzle potrebbe non essere abbastanza reale da poter essere preso sul serio; tuttavia, esistono altri scenari in cui brillano le comprensioni:
- La complessità di
Join
e GroupJoin
: l'ambito delle variabili di intervallo nelle join
clausole di comprensione delle query trasforma gli errori che altrimenti potrebbero essere compilati in sintassi punto in errori di tempo di compilazione nella sintassi di comprensione.
- Ogni volta che il compilatore introduce un identificatore trasparente nella trasformazione della comprensione, le comprensioni diventano utili. Ciò include l'uso di uno dei seguenti: più
from
clausole, join
e join..into
clausole e let
clausole.
Conosco più di un negozio di ingegneria nella mia città che ha messo fuorilegge la sintassi della comprensione. Penso che sia un peccato dato che la sintassi di comprensione non è che uno strumento e utile a questo. Penso che sia molto come dire: "Ci sono cose che puoi fare con un cacciavite che non puoi fare con uno scalpello. Poiché puoi usare un cacciavite come scalpello, gli scalpelli sono banditi d'ora in poi con il decreto del re."