DCSIMG
foreach - שלמה גולדברג (הרב דוטנט)

שלמה גולדברג (הרב דוטנט)

מרצה בסלע ויועץ בעולם ה - net.

foreach

מה זה foreach וכיצד מממשים אותו.
 
 
לאלו שנכנסים לעולם הפיתוח ולומדים #C, כשמגיעים ללולאות לומדים שיש כמה סוגים, כשהנפוצים הם, for ו - foreach,
ההסבר ללולאת for הוא די פשוט, לדוגמא:
 

int[] arr = { 1, 2, 3, 4, 5 };

for (var i = 0; i < arr.Length; i++)

{

    Console.WriteLine(arr[i];

}

 
מבצעים את שורת הקוד שבתוף גוף הלולאה כגודל המערך ובכל פעם מדפיסים מספר אחד מתוך המערך.
 
אותה תוצאה בתחביר foreach

foreach (int item in arr)

{

    Console.WriteLine(item);

}

 
איכשהו המשתנה item יהיה כל פעם הערך הבא במערך, ובפוסט הזה אני אסביר כיצד ממומש foreach.
 
ניקח לדוגמא את המחלקה הבאה
 

class Person

{

    public int Age { get; set; }

    public string Name { get; set; }

}

 

class PersonManager

{

    private List<Person> _persons;

 

    public PersonManager()

    {

        _persons = new List<Person>();

    }

}

 
כמובן שיש הרבה סיבות למה להשתמש ב - PersonManager ולא להשתמש ישירות באוסף של Persons, זה יכול להיות כדי לעשות בדיקות לפני שמוסיפים Person למערך או כל סיבה אחרת,
 
כעת נניח שיש לנו מופע של PersonManager, ונרצה איכשהו לרוץ על כל ה - Persons שהוא מחזיק, לא נוכל כמובן לרוץ בלולואת for, מכיוון שהמשתנה persons_ מוגדר כ - private ומסיבות מובנות ולא נרצה כמובן לחשוף אותו,
 
פיתרון סביר יהיה לגרום שנוכל לכתוב קוד כזה:
 

PersonManager manager = new PersonManager();

 

foreach (Person item in manager)

{

 

}

 
כמובן שממבט ראשון זה נראה מוזר, מכיוון ש - manager אינו מערך, אז כיצד נגרום לו לאפשר את הקוד הזה, גם הקומפיילר לא יאהב את זה וייתן את הודעת השגיאה
 
foreach statement cannot operate on variables of type 'ConsoleApplication7.PersonManager' because 'ConsoleApplication7.PersonManager' does not contain a public definition for 'GetEnumerator'
 
כדי לפתור זאת נצטרך לממש את IEnumerable
 

class PersonManager : IEnumerable

{

    private List<Person> _persons;

 

    public PersonManager()

    {

        _persons = new List<Person>();

    }

 

    IEnumerator GetEnumerator()

    {

 

    }

}

 
אנחנו רואים שאנחנו צריכים לממש מתודה בשם GetEnumerator שמחזירה מישהו שמממש את IEnumerator, (בהמשך אני אסביר מדוע צריך שני interfaces שונים)
 
כעת נייצר מחלקה חדשה שתמממש את ה - interface הנדרש, ובשביל הנוחות נגדיר את המחלקה החדשה בתוך המחלקה PersonManager.
 
ה - interface מוגדר כך:
 

public interface IEnumerator

{

    object Current { get; }

    bool MoveNext();

    void Reset();

}

 
המאפיין Current אמור להחזיר את האובייקט הנוכחי בריצה של הלולאה.
הפונקצייה MoveNext אמורה לגרום לכך ש - Current יצביע למיקום המתאים, ובנוסף להחזיר ערך בוליאני המגידר האם יש מקום לסיבוב נוסף של הלולאה.
לפונקציה Reset אין משמעות.
 
וכך זה יראה:
 

class PersonManagerEnumerator : IEnumerator

{

    private PersonManager _parent;

    private int _index = -1;

 

    public PersonManagerEnumerator(PersonManager manager)

    {

        _parent = manager;

    }

 

    public object Current

    {

        get

        {

            return _parent._persons[_index];

        }

    }

 

    public bool MoveNext()

    {

        _index++;

        return _index < _parent._persons.Count;

    }

 

    public void Reset()

    {

        throw new NotImplementedException();

    }

}

 
ב - ctor של המחלקה אחנו מקבלים מופע של PersonManager (ולכן חשוב שהמחלקה תהיה מוגדרת בתוך המחלקה PersonManager כך שתהיה גישה ל - private members), במתודה MoveNext אנחנו כל פעם מקדמים את ה - index שמוחזר על ידי ה - Current. (וזה דרך אגב הסיבה שאי אפשר לשנות ערכים של המערך ב - foreach, מכיוון שה - Current מוגדר רק עם get)
 
כעת נחזור למתודה הקודמת, שתראה כך:
 

public IEnumerator GetEnumerator()

{

    return new PersonManagerEnumerator(this);

}

 
 
הסיבה שצריך שני interface שונים, היא כדי לתמוך בלולאה בתוך לולאה, כי אם המחלקה עצמה הייתה מממשת את IEnumerator במידה ומישהו היה מבצע foreach בתוך foreach הוא לא היה מקבל את התוצאות הרצויות, כעת שכל מופע של foreach מקבל מופע של PersonManagerEnumrator משלו, זה לא יהווה בעייה.
 
 
שתי נקודות לסיום.
עדיף לממש את הגרסה הגנרית שלה (ודוגמא מלאה לקוד תוכלו לראות כאן)
בפוסט הבא אני אדגים שיטה יותר קצרה לממש foreach וזה בעזרת yield.
פורסם: Apr 21 2011, 06:53 AM by Shlomo | with 8 comment(s)
תגים:,

תוכן התגובה

שלמה גולדברג (הרב דוטנט) כתב/ה:

כמו שהבטחתי בפוסט הקודם אני אראה דרך קצרה יותר לממש את IEnumerable ללא מימוש עצמאי של IEnumerator בעזרת

# April 26, 2011 11:58 AM

Sasha Goldshtein כתב/ה:

זה לא נכון שלפונקציה Reset אין משמעות - היא אמורה לחזור לתחילת ה- enumerator. פשוט foreach לא משתמש בה.

# May 2, 2011 8:45 AM

Shlomo כתב/ה:

אז מי משתמש ב  - RESET

# May 2, 2011 9:30 AM

מאיר כתב/ה:

היי שלמה,

אתה כותב:

"לא נוכל כמובן לרוץ בלולואת for, מכיוון שהמשתנה persons_ מוגדר כ - private ומסיבות מובנות ולא נרצה כמובן לחשוף אותו,"

בעוד שבפועל בקוד לדוגמא הוא מוגדר כ public אחרת יש שגיאת קומפילציה.

יש אפשרות בכל זאת להשאיר אותו private?

# May 15, 2011 12:04 AM

Shlomo כתב/ה:

לא ברור לי למה אתה מתכוון, אני רואה שהוא מוגדר כ - private:

private List<Person> _persons;

# May 15, 2011 7:30 AM

מאיר כתב/ה:

בדוגמא שבאתר הוא אכן private אך בדוגמת הקוד המצורפת שגם מתקמפלת הוא public.

blogs.microsoft.co.il/.../download.aspx

# May 15, 2011 7:03 PM

Shlomo כתב/ה:

אתה צודק, טעות שלי.

במידה ומגדירים private צריך כמובן להגדיר מתודה Add בתוך ה - Person Manager

# May 15, 2011 9:33 PM
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 7 and 6 and type the answer here:


Enter the numbers above: