LINQ preferenza stile [chiuso]

18

Sono venuto a usare LINQ nella mia programmazione di tutti i giorni molto. Infatti, raramente, se mai, utilizzo un ciclo esplicito. Tuttavia, ho scoperto che non uso più la sintassi SQL come. Io uso solo le funzioni di estensione. Quindi piuttosto dicendo:

from x in y select datatransform where filter 

Io uso:

x.Where(c => filter).Select(c => datatransform)

Quale stile di LINQ preferisci e quali sono gli altri membri del tuo team con cui ti trovi a tuo agio?

    
posta Erin 10.06.2011 - 17:27
fonte

9 risposte

24

Trovo sfortunato che la posizione di Microsoft secondo la documentazione MSDN sia che la sintassi della query è preferibile, perché non la uso mai, ma uso sempre la sintassi del metodo LINQ. Adoro essere in grado di licenziare query one-liner per il contenuto del mio cuore. Confronto:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

A:

var products = Products.Where(p => p.StockOnHand == 0);

Più veloce, meno linee e ai miei occhi sembra più pulito. La sintassi delle query non supporta tutti gli operatori LINQ standard. Di recente, una query di esempio ha avuto un aspetto simile a questa:

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

Per quanto ne so, per replicare questa query usando la sintassi della query (nella misura del possibile) si direbbe così:

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

Non sembra più leggibile da me, e avresti bisogno di sapere comunque come usare la sintassi del metodo. Personalmente sono davvero innamorato dello stile dichiarativo che LINQ rende possibile e usarlo in qualsiasi situazione in cui sia possibile - forse a volte a mio discapito. Caso in questione, con la sintassi del metodo posso fare qualcosa di simile:

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

Immagino che il codice di cui sopra sarebbe difficile da capire per qualcuno che entra nel progetto senza una buona documentazione, e se non hanno un solido background in LINQ potrebbero non capirlo comunque. Tuttavia, la sintassi del metodo espone alcune potenzialità piuttosto potenti, in modo da velocizzare (in termini di righe di codice) una query per ottenere informazioni aggregate su più raccolte che altrimenti richiederebbero un sacco di noioso ciclo di foreach. In un caso come questo, la sintassi del metodo è ultra compatta per ciò che ne viene fuori. Provare a farlo con la sintassi della query potrebbe diventare poco pratico.

    
risposta data 11.06.2011 - 21:22
fonte
16

Trovo la sintassi funzionale più piacevole per gli occhi. L'unica eccezione è se ho bisogno di unire più di due set. The Join () diventa pazzo molto rapidamente.

    
risposta data 10.06.2011 - 17:38
fonte
8

È mai troppo tardi per aggiungere un'altra risposta?

Ho scritto una tonnellata di codice LINQ-to-objects e sostengo che almeno in quel dominio è bene comprendere entrambe le sintassi per poter usare qualsiasi codice per un codice più semplice - che non è sempre puntuale sintassi.

Naturalmente ci sono dei momenti in cui la sintassi dei punti IS è la strada da percorrere - altri hanno fornito molti di questi casi; tuttavia, penso che le comprensioni siano state cambiate rapidamente - dato un brutto colpo, se vuoi. Quindi fornirò un campione in cui ritengo che le comprensibilità siano utili.

Ecco una soluzione per un puzzle di sostituzione delle cifre: (soluzione scritta con 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);

... quali output:

N = 1, O = 6, K = 4

Non male, la logica scorre linearmente e possiamo vedere che arriva con un'unica soluzione corretta. Questo puzzle è abbastanza facile da risolvere a mano: ragionamento che 3 > N > 0 e O > 4 * N implica 8 > = O > = 4. Ciò significa che c'è un massimo di 10 casi da testare a mano (2 per N -by- 5 per O ). Ho smarrito abbastanza: questo puzzle è offerto per scopi illustrativi LINQ.

Trasformazioni del compilatore

C'è molto che il compilatore fa per tradurre questo in una sintassi dei punti equivalente. Oltre alle solite clausole seconde e successive from ottenute in% chiamateSelectMany abbiamo% clausole% co_de che diventano chiamate let con proiezioni, che utilizzano entrambi transparent-identifiers . Come sto per mostrare, il fatto di dover chiamare questi identificatori nella sintassi dei punti elimina la leggibilità di tale approccio.

Ho un trucco per esporre ciò che fa il compilatore nel tradurre questo codice nella sintassi dei punti. Se rimuovi il commento dalle due righe commentate sopra e lo esegui di nuovo, otterrai il seguente risultato:

N = 1, O = 6, K = 4

solution expression tree System.Linq.Enumerable+d_b8.SelectMany(O => Range(1, 8), (O, N) => new <>f_AnonymousType02(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12(<>h_TransparentIdentifier0 = <>h_TransparentIdentifier0, NO = ((10 * <>h_TransparentIdentifier0.N) + <>h_TransparentIdentifier0.O))).Select(<>h_TransparentIdentifier1 => new <>f_AnonymousType22(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32(<>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))

Mettere ciascun operatore LINQ su una nuova riga, traducendo gli identificatori "indicibili" con quelli che possiamo "parlare", cambiando i tipi anonimi nella loro forma familiare e modificando il gergo di espressioni Select in AndAlso espone il le trasformazioni che il compilatore fa per arrivare ad un equivalente in dot-syntax:

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 corri puoi verificare che emetta nuovamente:

N = 1, O = 6, K = 4

... ma scriverebbe mai un codice come questo?

Scommetto che la risposta è NONBHN (Not Only No, But Hell No!) - perché è troppo complessa. Certo, puoi trovare nomi di identificativi più significativi di "temp0" .. "temp3", ma il punto è che non aggiungono nulla al codice - non rendono il codice più performante, non lo fanno fai in modo che il codice legga meglio, solo il codice è brutto, e se lo facessi a mano, senza dubbio lo rovinerebbero una volta o tre prima di farlo bene. Inoltre, giocare a "il gioco del nome" è abbastanza difficile per gli identificatori significativi, quindi accolgo con favore la pausa dal gioco del nome che il compilatore mi fornisce nella comprensione delle query.

Questo esempio di rompicapo potrebbe non essere reale abbastanza da poter essere preso sul serio; tuttavia, esistono altri scenari in cui le query di comprensione brillano:

  • La complessità di && e Join : l'ambito delle variabili di intervallo nelle query di comprensione di query GroupJoin converte errori che potrebbero altrimenti compilare in dot-syntax in errori 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 una delle seguenti opzioni: più% clausole join , from & % clausole% enunciati e clausole join .

Conosco più di un negozio di ingegneria nella mia città natale che ha una sintassi di comprensione fuorilegge . Penso che questo sia un peccato perché la sintassi della comprensione è solo uno strumento e utile a questo. Penso che sia un po 'come dire, "Ci sono cose che puoi fare con un cacciavite che non puoi fare con uno scalpello. Perché puoi usare un cacciavite come scalpello, gli scalpelli sono vietati d'ora in poi sotto il decreto del re . "

    
risposta data 04.09.2012 - 05:31
fonte
7

Il mio consiglio è di utilizzare la sintassi di comprensione delle query quando l'intera espressione può essere eseguita nella sintassi di comprensione. Cioè, preferirei:

var query = from c in customers orderby c.Name select c.Address;

a

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

Ma preferirei

int count = customers.Where(c=>c.City == "London").Count();

a

int count = (from c in customers where c.City == "London" select c).Count();

Vorrei aver trovato una sintassi che rendesse più bello mescolare i due. Qualcosa come:

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

Ma purtroppo non l'abbiamo fatto.

In pratica, è una questione di preferenza. Fai quello che ti sembra migliore e ai tuoi colleghi.

    
risposta data 16.06.2011 - 19:24
fonte
3

SQL-like è un buon modo per iniziare. Ma dato che è limitato (supporta solo le costruzioni supportate dal tuo linguaggio corrente), alla fine gli sviluppatori passano allo stile dei metodi di estensione.

Vorrei sottolineare che ci sono alcuni casi che possono essere facilmente implementati in stile SQL.

Inoltre puoi combinare entrambi i modi in una query.

    
risposta data 10.06.2011 - 17:39
fonte
2

Tendo ad usare la sintassi non di query a meno che non sia necessario definire una variabile a metà della query come

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

ma scrivo la sintassi non di query come

x.Where(c => filter)
 .Select(c => datatransform)
    
risposta data 10.06.2011 - 18:06
fonte
2

Uso sempre le funzioni di estensione a causa dell'ordine. Prendi il tuo semplice esempio: in SQL, hai scritto seleziona per primo, anche se in realtà, dove è stato eseguito per primo. Quando scrivi usando i metodi di estensione, allora mi sento molto più in controllo. Ottengo Intellisense su ciò che è in offerta, scrivo le cose nell'ordine in cui accadono.

    
risposta data 11.06.2011 - 00:14
fonte
1

Anche a me piace la funzione di estensione.

Forse perché è meno un salto della sintassi nella mia mente.

Sembra più leggibile anche all'occhio, specialmente se stai usando framework di terze parti che hanno linq api.

    
risposta data 10.06.2011 - 23:32
fonte
0

Ecco l'euristica che seguo:

Favor LINQ expressions over lambdas when you have joins.

Penso che i lambda con i join siano disordinati e difficili da leggere.

    
risposta data 04.09.2012 - 05:49
fonte

Leggi altre domande sui tag