Filtraggio di cicli foreach con una condizione where vs continue clausole di guardia

23

Ho visto alcuni programmatori usare questo:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

invece di usare normalmente:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

Ho persino visto una combinazione di entrambi. Mi piace molto la leggibilità con "continua", soprattutto con condizioni più complesse. C'è anche una differenza nelle prestazioni? Con una query di database presumo che ci sarebbe. Che dire delle liste regolari?

    
posta Paprik 07.12.2015 - 13:56
fonte

2 risposte

61

Considererei questo come un luogo appropriato per utilizzare comando / separazione delle query . Ad esempio:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

Ciò consente anche di dare un buon nome autodocumentante al risultato della query. Ti aiuta anche a vedere le opportunità per il refactoring, perché è molto più semplice il codice refactor che interroga solo i dati o muta solo i dati rispetto al codice misto che tenta di eseguire entrambi.

Quando esegui il debug, puoi interrompere prima di foreach per verificare rapidamente se il contenuto di validItems si risolve in ciò che ti aspetti. Non è necessario entrare nel lambda a meno che non sia necessario. Se hai bisogno di entrare nel lambda, allora ti suggerisco di includerlo in una funzione separata, quindi scorri quella.

C'è una differenza nelle prestazioni? Se la query è supportata da un database, la versione LINQ ha il potenziale per essere eseguito più rapidamente, perché la query SQL potrebbe essere più efficiente. Se si tratta di LINQ to Objects, non si vedranno differenze di prestazioni reali. Come sempre, profila il tuo codice e correggi i colli di bottiglia effettivamente riportati, piuttosto che cercare di prevedere in anticipo le ottimizzazioni.

    
risposta data 07.12.2015 - 14:44
fonte
7

Ovviamente c'è una differenza nel rendimento, .Where() si traduce in una chiamata delegata effettuata per ogni singolo articolo. Tuttavia, non mi preoccuperei affatto delle prestazioni:

  • I cicli di clock utilizzati per invocare un delegato sono trascurabili rispetto ai cicli di clock utilizzati dal resto del codice che scorre sulla raccolta e controlla le condizioni.

  • La penalizzazione delle prestazioni di invocazione di un delegato è dell'ordine di alcuni cicli di clock e, fortunatamente, siamo lontani i giorni in cui dovevamo preoccuparci dei cicli di clock individuali.

Se per qualche ragione le prestazioni sono veramente importanti per te al livello del ciclo di clock, allora usa List<Item> invece di IList<Item> , in modo che il compilatore possa fare uso diretto ( e inlinable) chiama invece di chiamate virtuali e pertanto l'iteratore di List<T> , che in realtà è un struct , non deve essere inserito in una scatola. Ma è davvero roba da poco.

Una query di database è una situazione diversa, perché esiste (almeno in teoria) la possibilità di inviare il filtro all'RDBMS, migliorando così notevolmente le prestazioni: solo le righe corrispondenti faranno il viaggio dall'RDBMS al programma. Ma per quello penso che dovresti usare linq, non penso che questa espressione possa essere inviata al RDBMS così com'è.

Vedrai veramente i benefici di if(x) continue; nel momento in cui devi eseguire il debug di questo codice: il single-step su if() s e continue s funziona bene; il single-step nel delegato del filtro è un dolore.

    
risposta data 07.12.2015 - 14:22
fonte

Leggi altre domande sui tag