91. System Linq

ראינו בפעמים הקודמות קצת מהן Extension Methods ומה אפשר לעשות איתן.

בנוסף, בפעם האחרונה, גם ראינו שאפשר לשלוח delegate לExtension method לכל מיני שימושים.

בFramework 3.5 קיבלנו namespace שלם שנקרא System.Linq עם כל מיני Extension Methods שעובדים על כל IEnumerable.


עוד בFramework 2.0 חשבו על הרעיון של לשלוח delegateים למתודות למטרות סינון, ולראיה ניתן לראות את הפונקציה הבאות בList:

1
2
3
4
5
6
7
8
9
10
11
12
13
public bool Exists(Predicate<T> match);
public T Find(Predicate<T> match);
public void ForEach(Action<T> action);
public List<T> FindAll(Predicate<T> match);
public int FindIndex(Predicate<T> match);
public int FindIndex(int startIndex, Predicate<T> match);
public int FindIndex(int startIndex, int count, Predicate<T> match);
public T FindLast(Predicate<T> match);
public int FindLastIndex(Predicate<T> match);
public int FindLastIndex(int startIndex, Predicate<T> match);
public int FindLastIndex(int startIndex, int count, Predicate<T> match);
public int RemoveAll(Predicate<T> match);
public bool TrueForAll(Predicate<T> match);

לא נסביר על כל מתודה ומתודה, אבל, למשל Exists מקבלת פרדיקט (פונקציה שמקבלת T ומחזירה בוליאני), ומחזירה אמת אם קיים איבר שמקיים אותו

FindAll מקבלת פרדיקט ומחזירה את כל האיברים שמקיימים אותו

TrueForAll מקבלת פרדיקט ומחזירה אמת אם כל איברי הרשימה מקיימים אותו

ForEach רצה על כל איברי הרשימה ומבצעת על כל אחד מהם פעולה

לגבי השאר, אני מניח שתוכלו לנחש לבד…


הבעיה בסיפור הזה היא שהמתודות האלה יושבות בתוך מחלקה מאוד ספציפית שנקרא List<T>.

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

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

למשל תוכלו להסכים איתי שנוכל לממש לכל IEnumerable<T> את ForEach בצורה באה:

1
2
3
4
5
6
7
public void ForEach(Action<T> action)
{
foreach (T current inthis)
{
action(current);
}
}

ואת TrueForAll בצורה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
public bool TrueForAll(Predicate<T> match)
{
foreach (T current in this)
{
if (!match(current))
{
return false;
}
}
return true;
}

לכן בד"כ נאלץ לשכפל קוד, מה שתמיד רע.


הפתרון: Extension methods. במקום שכל אחד יצטרך לשכפל פונקציונאליות, הומצאו Extension Methods שעובדים על IEnumerable<T>שמבצעים את אותו הדבר בדיוק, למשל יכולנו לכתוב את המתודה TrueForAll כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static bool TrueForAll<T>(this IEnumerable<T> enumerable,
Predicate<T> match)
{
foreach (T current in enumerable)
{
if (!match(current))
{
return false;
}
}
return true;
}

ראו גם טיפ מספר 84.

למען האמת כתבו לנו את המתודה הזאת (עם חתימה מעט שונה) בשם All בnamespace ששמו System.Linq. בנוסף כתבו לנו עוד אוסף שלם של Extension Methods שמספק פונקציונאליות אדירה ונוחה בטירוף.

אחת המתודות שלא נמצא שם לא משנה כמה קשה נחפש, היא מתודה ששקולה למתודת הForEach שראינו. הסיבה היא שאחד הרעיונות שעומד מאחורי LINQ באופן כללי הוא תכנות פונקציונאלי. בין השאר, אחד העקרונות של תכנות פונקציונאליות הוא אובייקטים שהם immutable וstateless. מאחר ובד"כ בForEach נבצע מניפולציה על האוסף שלנו, הוחלט שלא יהיה בשבילו Extension Method.

בגדול הnamespace הזה הוא namespace מאוד שימושי, ואנחנו עוד נכיר אותו לעומק.

שבוע של שפה מובנית שאילתא טוב

שתף