Ar yra priežasties pakartotinai naudoti C # kintamąjį foreach?

Naudojant lambda išraiškas ar anoniminius metodus C #, turime būti atsargūs prieigai prie modifikuoto uždarymo spąstų. Pavyzdžiui:

 foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... } 

Dėl modifikuoto uždarymo pirmiau nurodytas kodas sukels visus atvejus, kai užklausos sąlygos yra pagrįstos galutine s reikšme.

Kaip paaiškinta čia , tai yra todėl, kad pirmiau minėtame foreach deklaruotas kintamasis verčia kaip kompiliatoriuje:

 string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... } 

vietoj to:

 while (enumerator.MoveNext()) { string s; s = enumerator.Current; ... } 

Kaip nurodyta čia , nėra jokio pranašumo, kad kintamasis būtų paskelbtas už ciklo ribų, ir įprastomis aplinkybėmis vienintelė priežastis, dėl kurios galiu tai padaryti, yra, jei planuojate naudoti kintamąjį už kilpos ribų:

 string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... } var finalString = s; 

Tačiau kintamieji, apibrėžti „ foreach negali būti naudojami už kilpos ribų:

 foreach(string s in strings) { } var finalString = s; // won't work: you're outside the scope. 

Taigi kompilatorius deklaruoja kintamąjį tokiu būdu, kad jis yra labai jautrus klaidoms, kurias dažnai sunku rasti ir derinti, nesulaukiant apčiuopiamos naudos.

Ar yra kažkas, ką galite padaryti su foreach , taip, kad negalėtumėte, jei jie būtų sukompiliuoti su kintamuoju su vidiniu regionu, ar tai tik savavališkas pasirinkimas, kuris buvo atliktas prieš anoniminius metodus ir lambda išraiškas ar bendras, o nuo to laiko nebuvo peržiūrėtas?

1480 m
17 янв. StriplingWarrior yra nustatytas 17 sausis 2012-01-17 20:21 '12 at 8:21 pm 2012-01-17 20:21
@ 5 atsakymai

Kompiliatorius deklaruoja kintamąjį tokiu būdu, kad jis yra labai jautrus klaidoms, kurias dažnai sunku rasti ir derinti, nesulaukiant apčiuopiamos naudos.

Jūsų kritika yra visiškai pagrįsta.

Čia išsamiai aptariu šią problemą:

Ciklo kintamojo uždarymas laikomas kenksmingu.

Ar yra kažkas, ką galite padaryti su foreach kilpomis taip, kad negalėtumėte, jei jie būtų sukompiliuoti su kintamuoju su vidiniu regionu? ar tai tik savavališkas pasirinkimas, kuris buvo atliktas prieš anoniminius metodus ir lambda išraiškas, kurios buvo prieinamos ar platinamos ir nuo to laiko nebuvo peržiūrėtos?

Paskutinis Specifikacija C # 1.0 iš tikrųjų nenurodė, ar kilpos kintamasis buvo kilpos viduje ar už jo ribų, nes jis nepadarė pastebimo skirtumo. Kai uždarymo semantika buvo įvesta C # 2.0, buvo pasirinkta kilpos kintamojo vieta už kilpos, atitinkanti „už“ kilpą.

Manau, teisinga pasakyti, kad visi apgailestauja dėl šio sprendimo. Tai yra vienas iš blogiausių „Cass“ „gotchų“, ir mes ketiname ištaisyti šią pataisą. C # 5, foreach ciklo kintamasis logiškai bus kilpos viduje, todėl uždarymas kiekvieną kartą gaus naują kopiją.

Kontūro kilpa nebus pakeista, o pakeitimai nebus perkelti į ankstesnes C # versijas. Todėl, naudodamiesi šia idioma, turite būti atsargūs.

1278
17 янв. Atsakymą pateikė Eric Lippert 17 sausis 2012-01-17 20:56 '12 8:56 val. 2012-01-17 20:56

Tai, ko prašote, savo dienoraštyje visiškai padengia Ericas Lippertas, o ciklo kintamąjį, kuris laikomas žalingu, ir jo tęstinumą.

Man labiausiai įtikinantis argumentas yra tai, kad naujo kintamojo kiekviename iteracijoje priėmimas prieštarauja for(;;) stiliaus kilpui. Ar tikitės kiekvienam iteracijai for (int i = 0; i < 10; i++) yra naujas int i ?

Dažniausia problema, susijusi su šiuo elgesiu, yra iteracijos kintamojo uždarymas ir tai yra paprastas būdas:

border=0
 foreach (var s in strings) { var s_for_closure = s; query = query.Where(i => i.Prop == s_for_closure); // access to modified closure 

Mano dienoraščio pranešimas apie šią problemą: uždarykite foreach kintamąjį C # .

174
17 янв. Atsakymas pateikiamas Krizz 17 jan. 2012-01-17 20:39 '12 8:39 pm 2012-01-17 20:39

Turėdamas šį įkandimą, turiu įprotį įtraukti vietiniu mastu nustatytus kintamuosius į vidinį span, kurį aš naudoju, kad galėčiau perkelti į bet kokį uždarymą. Savo pavyzdyje:

 foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure 

Aš:

 foreach (var s in strings) { string search = s; query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration. 

Kai turėsite šį įprotį, galite jį išvengti labai retais atvejais, kai ketinate prisijungti prie išorinių sričių. Sąžiningai, aš nemanau, kad kada nors tai padariau.

95
17 янв. atsakymą pateikė Godeke, sausio 17 d 2012-01-17 20:47 '12, 08:47 pm 2012-01-17 20:47

C # 5.0, ši problema yra išspręsta ir galite uždaryti kilpos kintamuosius ir gauti tikėtinus rezultatus.

Kalbos specifikacija nurodo:

8.8.4

(...)

A bet kuriai formai

 foreach (V v in x) embedded-statement 

tada plečiasi į:

 { E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } } 

(...)

v viduje esančio ciklo uždarymas yra svarbus tam, kad jis būtų užfiksuotas bet kokia anoniminė funkcija, atsirandanti po vandeniu išraiška. Pavyzdžiui:

 int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) f = () => Console.WriteLine("First value: " + value); } f(); 

Jei v buvo paskelbta už jos ribų, ji bus padalyta tarp visų iteracijų, o jos vertė po kilpos bus galutinė vertė, 13 , kuri yra tai, ką skambina f . Vietoj to, kadangi kiekviena iteracija turi savo kintamąjį v , pirmasis iteracijos metu f užfiksuotas skaičius ir toliau bus 7 , kuri bus atspausdinta. ( Pastaba: ankstesnės C # versijos yra paskelbtos v .

52
03 сент. Atsakymą pateikė Paolo Moretti 03 Sep. 2012-09-03 16:58 '12 at 16:58 2012-09-03 16:58

Mano nuomone, tai yra keistas klausimas. Gerai žinoti, kaip veikia kompiliatorius, bet tai tik „gerai žinoti“.

Jei rašote kodą, kuris priklauso nuo kompiliatoriaus algoritmo, tai yra bloga praktika. Ir tai geriau perrašyti kodą, kad pašalintumėte šią priklausomybę.

Tai geras darbo pokalbio klausimas. Bet realiame gyvenime aš nesu susidūręs su problemomis, dėl kurių nusprendžiau interviu.

90% foreach naudoja, kad apdorotų kiekvieną kolekcijos elementą (ne pasirinkti ar apskaičiuoti kai kurias vertes). Kartais reikia apskaičiuoti kai kurias vertes kilpoje, tačiau tai nėra gera praktika sukurti didelę kilpą.

Geriau naudoti LINQ išraiškas vertėms apskaičiuoti. Nes kai skaičiuojate daug dalykų ciklo viduje, po 2-3 mėnesių, kai jūs (ar kas nors kitas) perskaitėte šį kodą, asmuo nesupras, kas tai yra ir kaip ji turėtų veikti.

0
15 июня '18 в 10:31 2018-06-15 10:31 atsakymas į TemaTre birželio 15 d. 18 val. 10:31 am 2018-06-15 10:31