paint-brush
IEnumerable nav tas, ko jūs domājat — un tas pārkāpj jūsu koduautors@dmitriislabko
Jauna vēsture

IEnumerable nav tas, ko jūs domājat — un tas pārkāpj jūsu kodu

autors Dmitrii Slabko14m2025/02/18
Read on Terminal Reader

Pārāk ilgi; Lasīt

Sīkāk apskatīsim visbiežāk sastopamo kļūdu saistībā ar IEnumerable – atkārtotu uzskaitīšanu –, taču šoreiz iedziļināsimies nedaudz dziļāk un apskatīsim, kāpēc atkārtota uzskaitīšana ir kļūda un kādas potenciālas problēmas tas var radīt, tostarp grūti uztveramas un pavairojamas kļūdas.
featured image - IEnumerable nav tas, ko jūs domājat — un tas pārkāpj jūsu kodu
Dmitrii Slabko HackerNoon profile picture
0-item

TLDR: Detalizēti apskatīsim visizplatītāko kļūdu saistībā ar IEnumerable - atkārtotu uzskaitīšanu -, taču šoreiz mēs iedziļināsimies mazliet dziļāk un apskatīsim, kāpēc atkārtota uzskaitīšana ir kļūda un kādas potenciālās problēmas tas var radīt, tostarp grūti uztveramas un pavairojamas kļūdas.

Kas ir IEnumerable

Vispirms apskatīsim (vēlreiz, jo par to ir diezgan daudz rakstu), kas ir IEnumerable, gan vispārīgs, gan neparasts. Daudzi izstrādātāji, kā liecina daudzas intervijas un kodu apskati, neapzināti uzskata IEnumerable gadījumus kā kolekcijas, un ar to mēs sāksim.


Aplūkojot IEnumerable saskarnes definīciju, mēs redzam tālāk norādīto.

 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }


Mēs neiedziļināsimies skaitītāju detaļās un tā tālāk; pietiek pateikt vienu ļoti svarīgu lietu: IEnumerable nav kolekcija. Lielākā daļa kolekcijas veidu ievieš IEnumerable, taču tas nepārvērš visas IEnumerable implementācijas kolekcijās. Pārsteidzoši, tas ir tas, ko daudzi izstrādātāji palaiž garām, ieviešot kodu, kas patērē vai ražo IEnumerable, un tas ir tas, kam ir liels problēmu potenciāls.


Tātad, kas ir IEnumerable? Ir daudz dažādu IEnumerable implementāciju, taču vienkāršības labad mēs varam tos apkopot vienā (diezgan neskaidrā) definīcijā: tas ir koda gabals, kas rada elementus iterācijas laikā. Atmiņā esošajām kolekcijām šis kods vienkārši nolasa pašreizējo elementu no pamatā esošās kolekcijas un pārvieto tā iekšējo rādītāju uz nākamo elementu, ja tāds pastāv. Sarežģītākos gadījumos loģika var būt ļoti dažāda, un tai var būt jebkāda veida blakusparādības, kas var ietvert arī koplietojamā stāvokļa modifikāciju vai ir atkarīgas no koplietotā stāvokļa.


Tagad mums ir nedaudz labāks priekšstats par to, kas ir IEnumerable, un tas liek domāt, ka patērējošais kods ir jāievieš tā, lai nevajadzētu izdarīt nekādus pieņēmumus par šiem punktiem:

  • preces izgatavošanai nepieciešamās izmaksas - tas ir, ja prece tika izņemta no kāda veida krātuves (izmantota atkārtoti) vai tā tika izveidota;
  • vienu un to pašu vienumu var ražot atkal turpmākajās iterācijās;
  • jebkādas iespējamās blakusparādības, kas varētu ietekmēt (vai nē) turpmākās iterācijas.


Kā redzam, tas ir gandrīz pretējs vispārīgajām konvencijām, piemēram, atkārtojot atmiņā esošās kolekcijas:

  • kolekciju nevar modificēt iterācijas laikā — ja kolekcija tiek modificēta, tas radīs izņēmumu, pārejot uz nākamo kolekcijas elementu;
  • atkārtojot vienu un to pašu kolekciju (kurā ir tie paši elementi), vienmēr tiks iegūti vienādi rezultāti un vienmēr būs tādas pašas izmaksas.


Drošs veids, kā aplūkot IEnumerable, ir uztvert to kā “datu veidotāju pēc pieprasījuma”. Vienīgā garantija, ko šis datu ražotājs sniedz, ir tas, ka tas iegādāsies citu preci vai signalizēs, ka vairs nav pieejamas preces, kad tas tiks piezvanīts. Viss pārējais ir konkrēta datu ražotāja ieviešanas informācija. Starp citu, šeit mēs aprakstījām IEnumerator interfeisa līgumu, kas ļauj atkārtot IEnumerable gadījumu.


Vēl viens svarīgs datu veidotājs pēc pieprasījuma ir tas, ka tas ražo vienu vienību katrā iterācijā, un patēriņa kods var izlemt, vai tas vēlas izsmelt visu, ko ražotājs spēj saražot, vai pārtraukt patēriņu agrāk. Tā kā datu ražotājs pēc pieprasījuma pat nav mēģinājis strādāt pie potenciālajiem “nākotnes” priekšmetiem, tas ļauj ietaupīt resursus, kad patēriņš beidzas priekšlaicīgi.


Tātad, ieviešot IEskaitāmus ražotājus, mums nekad nevajadzētu izdarīt nekādus pieņēmumus par patēriņa modeļiem. Patērētāji var uzsākt un pārtraukt patēriņu jebkurā brīdī.

Atkārtotu iterāciju iespējamā ietekme.

Tagad, tā kā mēs definējām pareizo IEnumerable lietošanas veidu, apskatīsim dažus atkārtotu iterāciju piemērus un to iespējamo ietekmi.


Pirms ķeramies pie negatīviem piemēriem, ir vērts pieminēt, ka tad, kad IEnumerable uzdodas par atmiņā esošo kolekciju - masīvu, sarakstu, hashset utt. - atkārtotas iterācijas pašas par sevi nekaitē. Kods, kas patērē IEnumerable vairāk nekā atmiņā esošās kolekcijas, vairumā gadījumu darbotos (gandrīz) tikpat efektīvi kā kods, kas patērē atbilstošos kolekcijas veidus. Protams, atsevišķos gadījumos var būt atšķirības, lai gan ne vienmēr negatīvas, jo Linq ir pieredzējis daudzus nozīmīgus veiktspējas uzlabojumus, kas ļautu, piemēram, izmantot vektorizētas CPU instrukcijas atmiņā esošajām kolekcijām vai kompaktu vairāku interfeisa metožu izsaukumus vienā kompleksām Linq izteiksmēm. Lūdzu, izlasiet šos rakstus, lai iegūtu sīkāku informāciju: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#linq un https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/#linq


Tomēr no koda kvalitātes viedokļa vairāku iterāciju veikšana programmā IEnumerable tiek uzskatīta par sliktu praksi, jo mēs nekad nevaram būt droši, kāda konkrēta ieviešana tiks nodrošināta.

Sānu piezīme: tā kā IEnumerable ir interfeiss, tā izmantošana konkrētu veidu vietā liek kompilatoram emitēt virtuālo metožu izsaukumu ('callvirt' IL instrukcija), pat ja konkrēta pamatā esošā klase šo metodi ievieš kā nevirtuālu, tāpēc pietiktu ar nevirtuālās metodes izsaukumu. Virtuālo metožu izsaukumi ir dārgāki, jo tiem vienmēr ir jāiziet instanču metožu tabula, lai atrisinātu metodes adresi; arī tie novērš iespējamo metodes iekļaušanu. Lai gan to var uzskatīt par mikrooptimizāciju, ir diezgan daudz koda ceļu, kas parādītu dažādus veiktspējas rādītājus, ja saskarņu vietā tiktu izmantoti konkrēti veidi.

Ja atkārtota iterācija patiešām ir slikta izvēle.

Neliela atruna: šī piemēra pamatā ir reāla koda daļa, kas ir anonimizēta un kurā ir abstrahēta visa reālā ieviešanas informācija.

Šī koda daļa izguva datus no attālā galapunkta ienākošo parametru sarakstam.

 async Task<IEnumerable<IData>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalTasks = ids.Select(id => externalService.QueryForDataAsync(id, ct)); await Task.WhenAll(retrievalTasks); return retrievalTasks.Select(t => t.Result); }

Kas šeit var noiet greizi? Apskatīsim vienkāršāko piemēru:

 var results = await RetrieveAndProcessDataAsync(ids, cancellationToken); var output = results.ToArray();


Daudzi izstrādātāji šo kodu uzskatītu par drošu, jo tas novērš atkārtotas iterācijas, materializējot metodes izvadi atmiņas kolekcijā. Bet vai tā ir?


Pirms iedziļināties detaļās, veiksim testa braucienu. Pārbaudei varam izmantot ļoti vienkāršu 'externalService' implementāciju:

 record Data(int Value); class Service { private static int counter = 0; public async Task<IData> QueryForDataAsync(int id, CancellationToken ct) { var timestamp = Stopwatch.GetTimestamp(); await Task.Delay(TimeSpan.FromMilliseconds(30), ct); int cv = Interlocked.Increment(ref counter); Console.WriteLine($"QueryForData - id={id} - {cv}; took {Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds:F0} ms"); return new Data(id); } }


Pēc tam mēs varam palaist testu:

 var externalService = new Service(); var results = (await RetrieveAndProcessDataAsync([1, 2, 3], CancellationToken.None)).ToList(); Console.WriteLine("Querying completed"); int count = results.Count(); if (count == 0) { Console.WriteLine("No results"); } else { var array = results.ToArray(); Console.WriteLine($"Retrieved {array.Length} elements"); } Console.WriteLine($"Getting the count again: {results.Count()}");


Un iegūstiet izvadi:

 QueryForData - id=3 - 1; took 41 ms QueryForData - id=1 - 3; took 43 ms QueryForData - id=2 - 2; took 42 ms QueryForData - id=1 - 4; took 33 ms QueryForData - id=2 - 5; took 30 ms QueryForData - id=3 - 6; took 31 ms Querying completed Retrieved 3 elements Getting the count again: 3


Šeit kaut kas nav kārtībā, vai ne? Mēs būtu gaidījuši, ka “QueryForData” izvade tiks iegūta tikai 3 reizes, jo mums ir tikai 3 ID ievades argumentā. Tomēr izvade skaidri parāda, ka izpildes skaits dubultojās pat pirms ToList() izsaukšanas pabeigšanas.


Lai saprastu iemeslu, apskatīsim RetrieveAndProcessDataAsync metodi:

 1: var retrievalTasks = ids.Select(id => externalService.QueryForDataAsync(id, ct)); 2: await Task.WhenAll(retrievalTasks); 3: return retrievalTasks.Select(t => t.Result);


Un apskatīsim šo zvanu:

 (await RetrieveAndProcessDataAsync([1, 2, 3], CancellationToken.None)).ToList();


Kad tiek izsaukta metode RetrieveAndProcessDataAsync, notiek šādas lietas.


1. rindā mēs iegūstam IEnumerable<Task<Data>> gadījumu - mūsu gadījumā tie būtu 3 uzdevumi, jo mēs iesniedzam ievades masīvu ar 3 elementiem. Katru uzdevumu pavedienu pūls ievieto rindā izpildei, un, tiklīdz ir pieejams pavediens, tas sākas. Precīzs šo uzdevumu izpildes punkts nav noteikts pavedienu pūla plānošanas specifikas un konkrētās aparatūras dēļ, kurā šis kods darbotos.


2. rindā Task.WhenAll izsaukums nodrošina, ka visi uzdevumi no instances IEnumerable<Task<Data>> ir pabeigti; būtībā šajā brīdī mēs iegūstam pirmās 3 izvades no metodes QueryForDataAsync. Kad 2. rinda ir pabeigta, mēs varam būt pārliecināti, ka visi 3 uzdevumi ir izpildīti.


Tomēr 3. līnija ir vieta, kur visi velni slazda. Atklāsim tos.


Mainīgais 'retrievalTasks' (1. rindā) ir IEnumerable<Task<Data>> instance. Tagad spersim soli atpakaļ un atcerēsimies, ka IEnumerable nav nekas cits kā producents — koda gabals, kas ražo (izveido vai atkārtoti izmanto) noteikta veida gadījumus. Šajā gadījumā mainīgais “retrievalTasks” ir koda daļa, kas:


  • pārskatiet "ID" kolekciju;
  • katram šīs kolekcijas elementam tas izsauktu metodi externalService.QueryForDataAsync;
  • atgriezt iepriekšējā izsaukuma izveidoto uzdevumu gadījumu.


Mēs varam izteikt visu šo loģiku aiz mūsu IEnumerable<Task<Data>> instances nedaudz savādāk. Lūdzu, ņemiet vērā: lai gan šī koda daļa izskatās diezgan atšķirīga no sākotnējās izteiksmes ids.Select(id => externalService.QueryForDataAsync(id, ct)) , tā darbojas tieši tāpat.


 IEnumerable<Task<Data>> DataProducer(IList<int> ids, CancellationToken ct) { foreach (int id in ids) { var task = externalService.QueryForData(id, ct); yield return task; } }


Tātad mainīgo “retrievalTasks” varam uzskatīt par funkcijas izsaukumu ar nemainīgu iepriekš noteiktu ievades kopu. Šī funkcija tiks izsaukta katru reizi, kad mēs atrisināsim mainīgā vērtību. Mēs varam pārrakstīt metodi RetrieveAndProcessDataAsync tā, lai tā pilnībā atspoguļotu šo ideju un darbotos pilnīgi vienādi ar sākotnējo ieviešanu:


 async Task<IEnumerable<Data>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalFunc = () => DataProducer(ids, ct); await Task.WhenAll(retrievalFunc()); return retrievalFunc().Select(t => t.Result); }


Tagad mēs varam ļoti skaidri redzēt, kāpēc mūsu testa koda izvade tika dubultota: funkcija 'retrievalFunc' tiek izsaukta divreiz... Ja mūsu patērētais kods turpina darboties vienā un tajā pašā IEnumerable instancē, tas būtu vienāds ar atkārtotiem 'DataProducer' metodes izsaukumiem, kas katrai atkārtotai iterācijai atkal un atkal palaistu savu loģiku.


Es ceru, ka tagad IEnumerable atkārtoto iterāciju loģika ir skaidra.

Atkārtotu iterāciju turpmākās iespējamās sekas.

Tomēr par šo koda paraugu joprojām ir jāpiemin viena lieta.


Apskatīsim vēlreiz pārrakstīto ieviešanu:

 IEnumerable<Task<Data>> DataProducer(IList<int> ids, CancellationToken ct) { foreach (int id in ids) { var task = externalService.QueryForData(id, ct); yield return task; } } async Task<IEnumerable<Data>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalFunc = () => DataProducer(ids, ct); await Task.WhenAll(retrievalFunc()); // First producer call. return retrievalFunc().Select(t => t.Result); // Second producer call. }


Šajā gadījumā ražotājs katru reizi izveido jaunus uzdevumu gadījumus, un mēs to izsaucam divreiz. Tas noved pie diezgan savdabīga un ne tik acīmredzama fakta, ka, izsaucot Task.WhenAll un .Select(t => t.Result) , uzdevumu gadījumi, ar kuriem darbojas šie divi koda gabali, atšķiras. Uzdevumi, kas tika gaidīti (un līdz ar to tika pabeigti), nav tie paši uzdevumi, no kuriem metode atgriež rezultātus.


Tātad šeit ražotājs izveido divas dažādas uzdevumu kopas. Pirmā uzdevumu kopa tiek gaidīta asinhroni — zvans Task.WhenAll —, bet otrā uzdevumu kopa netiek gaidīta. Tā vietā kods izsauc tieši Result rekvizītu ieguvēju, kas faktiski ir bēdīgi slavenais sinhronizācijas un asinhronizācijas pretmodelis. Es neiedziļināšos detaļās par šo pretmodeli, jo šī ir liela tēma. Šis Stīvena Toba raksts par to sniedz daudz skaidrības: https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/


Tomēr pilnības labad tālāk ir norādītas dažas iespējamās problēmas, ko var izraisīt šis kods.

  • strupceļi, ja tiek izmantoti darbvirsmā (WinForms, WPF, MAUI) vai .Net Fx ASP.NET lietojumprogrammās;
  • vītņu baseina badošanās, ja tiek pakļauta lielākai slodzei.


Ja mēs abstrahējamies no pašreizējā koda parauga, kas veidoja šos vienkāršos uzdevumus, mēs saskaramies ar faktu, ka atkārtotas iterācijas var viegli izraisīt vairākas jebkuras darbības izpildes, un tā var nebūt idempotenta (t.i., nākamie izsaukumi ar vienādām ievadēm noteikti radīs dažādus rezultātus vai pat vienkārši neizdodas). Piemēram, konta bilances izmaiņas.


Pat ja šīs darbības būtu idempotentas, tām var būt augstas skaitļošanas izmaksas, un tādējādi to atkārtota izpilde vienkārši sadedzinātu mūsu resursus. Un, ja mēs runājam par kodu, kas darbojas mākonī, šiem resursiem var būt izmaksas, kas mums būtu jāmaksā.


Atkal, tā kā atkārtotas iterācijas IEskaitāmos gadījumos ir diezgan viegli palaist garām, var būt ļoti grūti noskaidrot, kāpēc lietojumprogramma avarē, tērē daudz resursu (tostarp naudu) vai dara lietas, kuras tai nevajadzētu darīt.

Nedaudz paspilgtinot lietas.

Ņemsim sākotnējo testa kodu un nedaudz mainīsim to:

 var externalService = new Service(); var cts = new CancellationTokenSource(); // New line. var results = (await RetrieveAndProcessDataAsync([1, 2, 3], cts.Token)); // Using cts.Token instead of a default token, and not materializing the IEnumerable. Console.WriteLine("Querying completed"); int count = results.Count(); if (count == 0) { Console.WriteLine("No results"); } else { var array = results.ToArray(); Console.WriteLine($"Retrieved {array.Length} elements"); } cts.Cancel(); // New line. Console.WriteLine($"Getting the count again: {results.Count()}");


Es atstāšu lasītāja ziņā izmēģināt un palaist šo kodu. Tas labi demonstrēs iespējamās blakusparādības, ar kurām var negaidīti saskarties atkārtotas iterācijas.

Kā labot šo kodu?

Apskatīsim:

 async Task<IEnumerable<IData>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalTasks = ids.Select(id => externalService.QueryForDataAsync(id, ct)).ToArray(); // Adding .ToArray() call. await Task.WhenAll(retrievalTasks); return retrievalTasks.Select(t => t.Result); }


Pievienojot vienu .ToArray() izsaukumu sākotnējam IEnumerable<Task<Data>> mēs 'materializētu' IEnumerable instanci atmiņā esošajā kolekcijā, un visas turpmākās atkārtotās atkārtošanās atmiņā esošajā kolekcijā veic tieši to, ko mēs domājam – vienkārši nolasa datus no atmiņas bez neparedzētām blakusparādībām, ko izraisa atkārtotas koda izpildes.


Būtībā, kad izstrādātāji raksta šādu kodu (kā sākotnējā koda paraugā), viņi parasti pieņem, ka šie dati ir “akmenī iecirsti”, un, kad tiem piekļūst, nekad nenotiks nekas negaidīts. Lai gan, kā mēs tikko redzējām, tas ir diezgan tālu no patiesības.


Mēs varētu vēl vairāk uzlabot metodi, bet mēs to atstāsim nākamajai nodaļai.

Par IEnumerable veidošanu.

Mēs tikko apskatījām problēmas, kas var rasties no IEnumerable izmantošanas, ja tas ir balstīts uz maldīgiem priekšstatiem - kad netiek ņemts vērā, ka, lietojot IEnumerable, nevajadzētu izdarīt nevienu no šiem pieņēmumiem:


  • preces izgatavošanai nepieciešamās izmaksas - tas ir, ja prece tika izņemta no kāda veida krātuves (izmantota atkārtoti) vai tā tika izveidota;
  • ja vienu un to pašu vienumu var ražot atkārtoti turpmākajās iterācijās;
  • jebkādas iespējamās blakusparādības, kas varētu ietekmēt (vai nē) turpmākās iterācijas.


Tagad apskatīsim solījumu, ko daudziem ražotājiem vajadzētu (ideālā gadījumā) turēt saviem patērētājiem:

  • preces tiek ražotas “pēc pieprasījuma” — nav jāpieliek nekādas pūles “iepriekš”;
  • patērētāji var jebkurā brīdī pārtraukt iterāciju, un tam vajadzētu ietaupīt resursus, kas būtu nepieciešami, ja patēriņš turpinātos;
  • ja iterācija (patēriņš) nav sākusies, resursi nav jāizmanto.


Atkal apskatīsim mūsu iepriekšējo koda paraugu no šī viedokļa.

 async Task<IEnumerable<IData>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalTasks = ids.Select(id => externalService.QueryForDataAsync(id, ct)).ToArray(); await Task.WhenAll(retrievalTasks); return retrievalTasks.Select(t => t.Result); }


Būtībā šis kods nepilda šos solījumus, jo viss smags darbs tiek veikts pirmajās divās rindās, pirms tas sāk ražot IEnumerable. Tātad, ja kāds patērētājs nolemtu pārtraukt patēriņu agrāk vai pat nesāktu to vispār, QueryForDataAsync metode joprojām tiktu izsaukta visiem ievadiem.


Ņemot vērā pirmo divu rindu darbību, būtu daudz labāk pārrakstīt metodi, lai izveidotu atmiņā esošu kolekciju, piemēram:

 async Task<IList<IData>> RetrieveAndProcessDataAsync(IList<int> ids, CancellationToken ct) { var retrievalTasks = ids.Select(id => externalService.QueryForDataAsync(id, ct)).ToArray(); await Task.WhenAll(retrievalTasks); return retrievalTasks.Select(t => t.Result).ToArray(); }


Šī ieviešana nesniedz nekādas garantijas pēc pieprasījuma - gluži pretēji, ir ļoti skaidrs, ka viss nepieciešamais darbs, lai apstrādātu doto ievadi, tiktu pabeigts un atbilstošie rezultāti tiktu atgriezti.


Tomēr, ja mums ir nepieciešama “datu veidotāja pēc pieprasījuma” darbība, metode būtu pilnībā jāpārraksta, lai to nodrošinātu. Piemēram:

 async IAsyncEnumerable<Data> RetrieveAndProcessDataAsAsyncEnumerable(IList<int> ids, [EnumeratorCancellation] CancellationToken ct) { foreach (int id in ids) { var result = await externalService.QueryForData(id, ct); yield return result; } }


Lai gan izstrādātāji parasti nedomā par šīm IEnumerable līguma specifikām, citi kodi, kas to patērē, bieži vien izdarītu pieņēmumus, kas atbilst šai specifikai. Tātad, ja kods, kas veido IEnumerable, atbilst šīm specifikācijām, visa lietojumprogramma darbosies labāk.

Secinājums.

Es ceru, ka šis raksts palīdzēja lasītājam saskatīt atšķirību starp iekasēšanas līgumu un IEnumerable līguma specifiku. Kolekcijas parasti nodrošina zināmu krātuvi saviem vienumiem (parasti atmiņā) un veidus, kā pārskatīt saglabātos vienumus; nelasāmas kolekcijas arī pagarina šo līgumu, ļaujot modificēt/pievienot/noņemt uzglabātās preces. Lai gan kolekcijas ir ļoti konsekventas attiecībā uz saglabātajiem vienumiem, IEnumerable būtībā deklarē ļoti augstu nepastāvību šajā ziņā, jo vienumi tiek radīti, atkārtojot IEnumerable gadījumu.


Tātad, kāda būtu labākā prakse, ienākot IEnumerable? Dosim tikai punktu sarakstu:

  • Vienmēr izvairieties no atkārtotām iterācijām – ja vien tas nav tas, ko jūs patiešām plānojat un nesaprotat sekas. Ir droši savienot vairākas Linq paplašinājuma metodes ar IEskaitāmu gadījumu (piemēram, .Where un .Select ), taču no jebkura cita izsaukuma, kas izraisītu faktisku iterāciju, ir jāizvairās. Ja apstrādes loģikai ir nepieciešamas vairākas IEnumerable pārejas, vai nu materializējiet to atmiņā esošā kolekcijā vai pārbaudiet, vai loģiku var mainīt uz vienu pāreju katram vienumam.
  • Ja IEnumerable izveide ietver asinhrono kodu, apsveriet iespēju to mainīt uz IAsyncEnumerable vai aizstāt IEnumerable ar "materializētu" attēlojumu, piemēram, ja vēlaties izmantot paralēlās izpildes priekšrocības un atgriezt rezultātus pēc visu uzdevumu pabeigšanas.
  • Kodu veidojošais IEnumerable jāveido tā, lai izvairītos no resursu tērēšanas, ja iterācija apstātos agrāk vai nesāktos vispār.
  • Neizmantojiet IEnumerable datu tipiem, ja vien jums nav nepieciešama tā specifika. Ja jūsu kodam ir nepieciešama zināma “vispārināšana”, dodiet priekšroku citām kolekcijas veida saskarnēm, kas nenorāda uz “datu veidotāja pēc pieprasījuma” darbību, piemēram, IList vai IReadOnlyCollection.


L O A D I N G
. . . comments & more!

About Author

Dmitrii Slabko HackerNoon profile picture
Dmitrii Slabko@dmitriislabko
Accomplished software developer with 25+ years of experience. Focus on .NET technologies.

PAKARINĀT TAGUS

ŠIS RAKSTS TIKS PĀRSTRĀDĀTS...