כאשר חלק מהמתכנתים מביטים לראשונה על שאילתות Linq , הם נראות להם מאיימות.
בפוסט זה אסביר איך בעצם ממומשים מתודות Linq "מאחורי הקלעים" - באמצעות הדגמה על Where.
למשל: יש לנו collection של מספרים:
בפוסט זה אסביר איך בעצם ממומשים מתודות Linq "מאחורי הקלעים" - באמצעות הדגמה על Where.
למשל: יש לנו collection של מספרים:
var nums = new[] {1,2,3,4,5,6,7,8,9,10};
אם נרצה לקבל את כל המספרים האי זוגיים, אז באמצעות Linq נבצע:
result = nums.Where(n => n % 2 != 0);
בואו נעשה drill-down ונבין איך זה ממומש. נתחיל מהתחלה:
נממש את פונקציית FilterIntegers שמקבלת רשימה של מספרים, ומחזירה רשימה חדשה רק של המספרים האי זוגיים.
הפונקציה נעזרת בפונקציית Predicate שמטרתה לבדוק אם המספר הוא אי-זוגי:
וכעת אם נבצע את הקריאה הבאה:
נוסיף עוד פונקציית מיון (לשם הדוגמא נכתוב פונקצייה שמחזירה מספרים זוגיים:
כעת נוכל לבצע פילטור כך:
ונוכל לשלוח ב delegate מצביע לאיזה פונקציית סינון שנרצה.
בואו נתקדם עוד שלב:
נשנה את הפונקציה FilterIntegers כך שתהיה יותר גנרית, ונשנה לה את השם ל Filter, כי כעת היא תוכל לסנן רשימה של כל Type:
כעת רק כדי שיהיה לנו יותר קל (ולא נצטרך ליצור delegate) נשתמש ב Func כמו שראינו בפוסט הקודם:
ונקרא כך לפונקציה שלנו:
ה delegate שלנו יצביע על פונקציית הסינון (הפעם נסנן מספרים אי-זוגיים):
Func<int, bool> predicateDelegate = IsEven;
ונפעיל את הפונקציה:
אז מה יש לנו עד עכשיו?
פונקצייה גנרית בשם Filter, שמקבלת רשימה של אובייקטים, ומקבלת delegate לפונקצייה, ומחזירה לנו רשימה של האיברים שעונים על התנאי של הפונקציה שאליה מצביע ה delegate.
נמשיך-
במקום שה delegate שלנו יצביע לפונקציה שהיא בעצם ממומשת ע"י שורה אחת, נוכל להשתמש ב anonymous methods:
ואפשר לכתוב את הנ"ל בכתיב מקוצר:
אז במקום לשלוח לפונקציה שלנו את predicateDelegate, אנחנו יכולים ישר לשלוח את ה anonymous method שלנו:
עכשיו אנחנו ממש מתקרבים למימוש האמיתי של Where.
בעצם נותר לנו לבצע 2 פעולות:
נממש את פונקציית FilterIntegers שמקבלת רשימה של מספרים, ומחזירה רשימה חדשה רק של המספרים האי זוגיים.
הפונקציה נעזרת בפונקציית Predicate שמטרתה לבדוק אם המספר הוא אי-זוגי:
/// <summary>
/// return IEnumerable that contains only the numbers that are not Even
/// </summary>
/// <param name="nums"></param>
/// <returns></returns>
private static IEnumerable<int> FilterIntegers(IEnumerable<int> nums)
{
var result = new List<int>();
foreach (var item in nums)
{
if (Predicate(item))
{
result.Add(item);
}
}
return result;
}
/// <summary>
/// return true if the item is not Even.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public static bool Predicate(int item)
{
return item % 2 != 0;
}
var result = FilterIntegers(nums);
אז נקבל אל result את רשימת המספרים האי זוגיים.
איך אפשר לשפר את הפונקציה?
בואו נאמר שאנחנו רוצים גם פונקציה שתחזיר לנו את המספרים הזוגיים. הרי לא נכתוב עוד פונקציה לכך נכון? אז נשתמש באותה הפונקציה-FilterIntegers ופשוט נעביר לפונקציה delegate שיצביע לפונקציית סינון כלשהי, וכך FilterIntegers יידע לסנן רשימת מספרים ע"י כל פונקציית סינון שנחליט.
זה ייראה כך:
private static IEnumerable<int> FilterIntegers(IEnumerable<int> nums,PredicateDelegate<int> predicateDelegate )
{
var result = new List<int>();
foreach (var item in nums)
{
if (predicateDelegate(item))
{
result.Add(item);
}
}
return result;
}
נוסיף עוד פונקציית מיון (לשם הדוגמא נכתוב פונקצייה שמחזירה מספרים זוגיים:
public static bool IsEven(int item)
{
return item % 2 == 0;
}
public delegate bool PredicateDelegate<T1>(T1 value);
כעת נוכל לבצע פילטור כך:
//point the delegate to any function PredicateDelegate<int> myDelegate = IsEven; var result = FilterIntegers(nums,myDelegate);
ונוכל לשלוח ב delegate מצביע לאיזה פונקציית סינון שנרצה.
בואו נתקדם עוד שלב:
נשנה את הפונקציה FilterIntegers כך שתהיה יותר גנרית, ונשנה לה את השם ל Filter, כי כעת היא תוכל לסנן רשימה של כל Type:
public static IEnumerable<T1> Filter<T1>(IEnumerable<T1> nums, PredicateDelegate<T1> predicateDelegate)
{
var result = new List<T1>();
foreach (T1 item in nums)
{
if (predicateDelegate(item))
{
result.Add(item);
}
}
return result;
}
כעת רק כדי שיהיה לנו יותר קל (ולא נצטרך ליצור delegate) נשתמש ב Func כמו שראינו בפוסט הקודם:
public static IEnumerable<T1> Filter<T1>(IEnumerable<T1> list, Func<T1, bool> predicateDelegate)
{
var result = new List<T1>();
foreach (T1 item in nums)
{
if (predicateDelegate(item))
{
result.Add(item);
}
}
return result;
}
ונקרא כך לפונקציה שלנו:
ה delegate שלנו יצביע על פונקציית הסינון (הפעם נסנן מספרים אי-זוגיים):
Func<int, bool> predicateDelegate = IsEven;
ונפעיל את הפונקציה:
result= Filter(nums,predicateDelegate)
אז מה יש לנו עד עכשיו?
פונקצייה גנרית בשם Filter, שמקבלת רשימה של אובייקטים, ומקבלת delegate לפונקצייה, ומחזירה לנו רשימה של האיברים שעונים על התנאי של הפונקציה שאליה מצביע ה delegate.
נמשיך-
במקום שה delegate שלנו יצביע לפונקציה שהיא בעצם ממומשת ע"י שורה אחת, נוכל להשתמש ב anonymous methods:
predicateDelegate = item => { return item % 2 == 0 ; };
ואפשר לכתוב את הנ"ל בכתיב מקוצר:
predicateDelegate = item => item % 2 == 0;
אז במקום לשלוח לפונקציה שלנו את predicateDelegate, אנחנו יכולים ישר לשלוח את ה anonymous method שלנו:
result= Filter(nums, item => item % 2 == 0);
עכשיו אנחנו ממש מתקרבים למימוש האמיתי של Where.
בעצם נותר לנו לבצע 2 פעולות:
- לשנות את הפונקציה ל Extension method- כדי להפוך את הפונקציה ל Extension method פשוט נוסיף את המילה this לפני הארגומנט הראשון, ונעביר את הפונקציה ל class סטטי.
- לבצע yield return מהפונקציה- כך לא צריך לחכות עד שהפונקציה תסיים לחשב את מה שהיא צריכה, ועל כל איבר שהיא החליטה אם הוא תקין, אז היא תחזיר אותו (או יותר נכון Ienumerable )
public static IEnumerable<T1> Filter<T1>( this IEnumerable<T1> list, Func<T1, bool> predicateDelegate)
{
var result = new List<T1>();
foreach (T1 item in nums)
{
if (predicateDelegate(item))
{
yield return item;
}
}
}
וזהו! סיימנו! מימשנו את פונקציית Where.
כעת נוכל לקרוא לפונקציית Filter:
כעת נוכל לקרוא לפונקציית Filter:
result= nums.Filter( item => item % 2 == 0);
שזה בדיוק כמו:
result= nums.Where( item => item % 2 == 0);
תהנו!




