Minek annyi foreach?

Ha elfogadjuk azt az alaptételt, hogy “kevesebb kód – kevesebb bug”, akkor miért ragaszkodunk annyira a szószátyár foreach ciklusokhoz?

Számtalanszor látok olyan kódot, ami egy lista minden elemével csinál valamit, gyakran csak átadja egy metódusnak:

  List<string> vezerek = new List<string> { "Álmos", "Előd", "Ond", "Kond", "Tas", "Huba", "Töhötöm" };
  foreach( var vezer in vezerek )
  {
    Console.WriteLine( vezer );
  }

Ennél az alábbi sokkal egyszerűbb és pont ugyanezt csinálja:

  vezerek.ForEach( vezer => Console.WriteLine( vezer ) );

Persze olyan is van, amikor nem minden elemmel akarjuk elvégezni ezt a műveletet, csak azokkal, ami teljesít egy feltételt:

  foreach( var vezer in vezerek )
  {
    if( vezer.EndsWith( "d" ) )
    {
      Console.WriteLine( vezer );
    }
  }

Ennél is van egyszerűbb:

  vezerek.FindAll( vezer => vezer.EndsWith( "d" ) ).ForEach( vezer => Console.WriteLine( vezer ) );

Az első példában egyértelmű, hogy a rövidebb megoldást célszerű alkalmazni, mert pont ugyanazt csinálja a két kód, csak az egyik rövidebb és jobban olvasható. A második esetben azonban a FindAll és a ForEach egyaránt egy-egy for ciklusra fordul, tehát itt már két ciklus fog lefutni egymás után – azaz  mérlegelnünk kell az olvashatóság és a teljesítmény között. Például nem biztos, hogy ez a legjobb megoldás egy CheckBoxList kiválasztott elemeinek DataTable-be töltésére:

  DataTable dt = new DataTable();
  dt.Columns.Add( "Item", typeof( string ) );
  this.cblNames.Items
.Cast<ListItem>()
.ToList()
.FindAll( item => item.Selected )
.ForEach( item => dt.Rows.Add( item.Value ) );

Ha eltekintünk a teljesítménytől, akkor is fontos szempont, hogy sokak számára a fenti lambdás írásmód még nem számít “olvashatónak”, ugyanúgy ahogy a (feltétel) ? (ha igaz) : (ha hamis) szintakszis (van ennek valami szép neve?) sem. Mindkettő túl tömör. Azonban van egy óriási előnyük: ordít róluk, hogy mire szolgál az adott kódsor. A foreach csak egy ciklus, a ForEach pedig egy gyűjtemény minden elemével elvégez egy műveletet. Az if csak egy feltétel vizsgálat, a (feltétel) ? (ha igaz) : (ha hamis) viszont egy feltételtől függő értékadás. Kétségkívül kell hozzá kis gyakorlat, de megéri.

Ti mit gondoltok, olvashatóbb a tömörebb kód?

Technorati-címkék: ,

9 thoughts on “Minek annyi foreach?

  1. Attila

    Általában olvashatóbb, feltéve, hogy a fejlesztő (olvasó) számára rutinból ismert az adott szintaktika.Egy dologot azért nem tud (legalábbis szépen) a lambda: a típuskényszerítést. Példa:class A {}class B : A {}class C { public IEnumerable<A> AList; }foreach (B b in c.AList) //explicit típus kényszerítés, feltételezi, hogy AList minden eleme B típusú{}ez most durva példa de a régi Hashtable és társainál (object típusú elemek enumerációi) igen hasznos!Szerintem ez kifejezésben leírva már nem olyan szép (és a mögöttes IL kódra is kiváncsi lennék):c.AList.Select(a => (B)a).Where(b => b.IsTrue).ForEach(b => Debug.WriteLine(b));

  2. Csaba

    A rövidebb kód olvashatóbb, de a 2-3 soros foreachnél nem sokkal rövidebb a lambda expressionös formátum. Ilyenkor viszont az dönt, hogy ki hogy látja át a kódot, azaz mennyire olyan, mint amit ő szokott írni, azaz mennyire elterjedt a használata.Sokszor mi is a ? : operátort használjuk, sőt van, amikor ? : ? : -ot, azaz az A?B:C-nél a C megint ?:-ba fordul. Rövid, tömör, de mégis annyi benne az operátor, hogy van, amikor if else if-et csinálunk belőle.A forach és List<>.ForEach viszont előrelépés a régi Delphi-s programozók for ciklusaihoz, ott 6-7 sor -> 1-2 sor már olvashatóságot növel.

  3. Péter

    Megoldás a feltételes ForEach-re:public static void ForEach<T>(this IEnumerable<T> enumerable, Predicate<T> predicate, Action<T> action){ Contract.Requires(enumerable != null); Contract.Requires(predicate != null); Contract.Requires(action != null); foreach (T item in enumerable) { if (predicate(item)) action(item); }}

  4. György

    Péter: Látom rákattantál a code contracts-re🙂 Egyébként erre a feltételes ForEach-re én is eljutottam, és nagyon nem értem, hogy miért nincs benne a Frameworkben.

  5. Péter

    Én most azt nem értem ,hogy a 4-es Frameworkben a ForEach miért nem egy extension method az IEnumerable<T>-hez. Miért csak kizárólag a List<T> tudja ezt (még nem is az IList<T>)?

  6. Balázs

    Nálunk az a főszabály, hogy a fenti kiterjesztési műveleteket olyankor illik használni, ha a listát elemenként dolgozzuk fel, azaz ha nincs a műveleteknek mellékhatása. Ha már valami komoly változást okozunk a külvilágban (pl. stream-re írunk, adatbázisba mentünk, kivételt dobálunk, stb), akkor illik az imperatív szerkezetek használata. Így nemcsak hogy kihasználjuk a kényelmes, új szerkezeteket, ráadásul még az is eldönthető ránézésre, hogy az adott kód valaminek a kiszámítása, vagy végrehajtása lesz-e.

  7. unbornchikken

    Azé beteg ez a lamda forícs, mert ott bizony egy delegate hívás fog fordítódni, ami sokkal lassabb ebben a példában mint a sima foreach. Próbáljátok ki!

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s