Monday, September 20, 2021 19:52

Cuprins >> LINQ > Executarea amânată

Executarea amânată

Mulți programatori par să creadă că executarea amânată (în engleză, deferred execution) există din cauza LINQ, iar acest lucru este doar parțial adevărat. LINQ are multe de-a face cu aceasta, dar execuția amânată există în primul rând din cauza declarației yield.

Dar vorbesc despre executarea amânată și nu am explicat-o, cel puțin conceptual, și o voi face folosind contextul LINQ, pentru simplitate.

Când avem unele interogări LINQ, chiar și înlănțuite, indiferent cât de lungi, acestea sunt executate numai atunci când le spunem în mod explicit să facă acest lucru. Deci, știți în acest moment că LINQ este utilizat pentru a manipula colecțiile de date într-un fel de linie de producție, unde o funcție ia o colecție, face ceva cu ea și o trimite mai departe la o altă funcție. Ceea ce nu știți este faptul că nu se face deloc o enumerare a unei interogări, și o interogare LINQ este doar o simplă interogare, până la punctul în care îi cerem în mod explicit să fie executată.

Pentru a demonstra acest lucru, voi folosi codul din lecția anterioară, unde am exemplificat cum putem scrie propriile versiuni ale funcțiilor LINQ Where() și Select(), dar de data aceasta le voi face generice, așa cum sunt în LINQ. În plus, voi pune două declarații pentru afișarea unui mesaj la consolă, doar pentru a putea verifica faptul că am apelat efectiv cele funcții și au fost executate.


În acest moment, încercați să anticipați care va fi rezultatul. Nu fac o iterație asupra elementelor lui rezultat pentru a le afișa la consolă, cu toate acestea, apelez atât Where(), cât și Select(), deci ar trebui să avem cel puțin textul din interiorul lor afișat la consolă. Să verificăm:

Greșit! Consola nu afișează NIMIC. Nu este afișat nici „Folosesc Where()”, nici „Folosesc Select()”. Parcă acele funcții nici măcar nu sunt apelate! Și asta este adevărat. Puteți încerca chiar voi înșivă, setați două breakpoint-uri, unul în funcția Where(), celălalt în interiorul Select(), rulați programul, iar breakpoint-urile nu vor fi atinse.

Dacă vă amintiți din lecția despre yield, am spus acolo că ori de câte ori folosim yield return într-o funcție, codul din acea funcție este transformat de compilator într-un bloc iterator și este convertit practic într-o clasă, iar compilatorul returnează o instanță a acelei clase, și așa mai departe.

Deci, practic, nu am primit nici o ieșire deoarece nu am început să consumăm rezultatele, atât codurile din Where(), cât și Select() nu sunt executate până când nu începem să consumăm elementele rezultat.

O voi face acum, dar în loc să folosesc o buclă foreach, voi folosi de fapt enumeratorul enumerabilului. Dacă nu știți ce este acesta, citiți lecția despre IEnumerable și IEnumerator mai întâi.

Vizualizează schimbări cod


Legendă:

  • liniile verzi cu un semn plus lângă numerele liniilor sunt linii noi adăugate
  • liniile rosii cu un semn minus lângă numerele liniilor sunt linii vechi șterse



Acum, dacă setez un breakpoint după interogarea LINQ, putem observa același efect ca înainte, nu este furnizată nicio ieșire:

Chiar și după ce cer enumeratorul, nu se întâmplă nimic:

Dar acum, dacă apăs tasta F11 la tastatură sau apăs pe butonul Step Into din Visual Studio, execuția sare aici:

Acum se află în funcția Select() care reprezintă de fapt funcția MoveNext() pentru clasa generată de compilator a blocului iterator.

Deci, până când am început de fapt să-i cer enumeratorului să-mi dea valorile enumerabilului, nu s-a executat nicio interogare, nu s-a apelat nici o funcție, de fapt, execuția a fost amânată până în ultimul moment, când de fapt aveam nevoie de ea. După ce iteratorul iterează toate datele, obținem rezultatul scontat și vedem că cele două funcții au fost apelate:

Tags: , , , ,

Leave a Reply



Follow the white rabbit