Predicate, Action, Func Delegates

סגור באמצעות טופס זה תוכלו לספר ולהמליץ לחבריכם..
שם השולח:
כתובת דוא"ל של השולח:
שם המקבל:
שלח לכתובת דוא"ל:
הוסף הערה:
 
 
Extension Method  Predicate<T>, Action<T>, Func<T>
 
מאת: ארז קלר


הורדת דוגמאות קודהורדת דוגמאות קוד


.NET Framework 2.0 הציג לעולם הפיתוח את ה- Generics, את ה- Anonymous Methods ואת ה-Nullable Types.
.NET Framework 3.0 הביא עמו תכונות ויכולות רבות נוספות: Linq, Lambda Expression, Extension Methods, Implicit Types ועוד....
.NET Framework 4.0 חשף את ה- Dynamic, Optional Parameters ועוד....
מבין שלל התכונות החדשות התווספו למרחב השמות System מספר Generic Delegates מובנים שקצת עשו פחות רעש, וקיבלו מעט מדי פרסום.
מעט מדי "יחסי ציבור" בהתחשב בתרומה שלהם ליכולות החדשות של השפה והתשתית.
חלק מה- Generic Delegates התווספו לתשתית כבר בגרסה 2.0 של התשתית, רובם בגרסה 3.5 ומיעוטם רק בגרסה 4.0.
מדובר על ה-Delegates הבאים:

 
  • Predicate<T>
  • Action<T> - על שלל העמסותיו (Overloading)
  • Func<T> - על שלל העמסותיו (Overloading)
 
Predicate<T> Delegate
Predicate delegate קצת שונה מהאחרים, הוא לא מועמס, הוא תמיד מקבל פרמטר בודד והוא מחזיק ערך בוליאני.
החתימה המלאה של ה-delegate כפי שהוא מוגדר במרחב השמות System:
(public delegate bool Predicate<in T>(T obj
 
באמצעות השימוש ב-Predicate נוכל להגדיר קריטריונים לחיפוש ומיון באוסף אובייקטים.
המחלקות Array ו- List<T> משתמשות בו לא מעט.
לדוגמה: המתודה List<T>.FindAll(…) אשר מחזירה את רשימת האובייקטים אשר עומדים בקריטריון מוגדר.
 
החתימה המלאה של המתודה:
public List<T> FindAll(Predicate<T> match)
 
באמצעות Predicate<T> נגדיר את הקריטריונים שעל פיהם המתודה FindAll תפעל.
לדוגמה: נכתוב תוכנית שמדפיסה את כל המספרים הגדולים מ-10 מתוך רשימה נתונה,
נציג את הפיתרון בשלושה אופנים שונים:
שימוש מסורתי ב-delegates כפי שהיה מקובל ב-.NET Framework 1.0 , שימוש ב- Anonymous Methods ושימוש ב- Lambda Expression.
הערה:
אם אתם מכירים Lambda Expression גשו ישר לחלק השלישי,
אם אתם לא מכירים Lambda Expression אבל שולטים ב- Anonymous Methods החלק השני מיועד לכם (ומיד אחר כך רוצו ללמוד למבדה, ממש כאן)
אם אתם לא מכירים לא את Lambda Expression ולא את Anonymous Methods החלק הראשון מיועד עבורכם ומיד רוצו ללמוד Anonymous Methods ו-Lambda Expression (על פי הסדר)
רוצו ללמוד כי בהמשך הדוגמאות יוצגו רק באמצעות Lambda Expression.
דוגמת קוד- PredicateSample01:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         List<int> list = new List<int> { 1, 43, 6, 12, 5, 23, 7, 86, 3, 49 }; 
 6  :         // C# 1.0 style 
 7  :         List<int> greater_than_10 = list.FindAll(GreaterThanTen); 
 8  :         foreach (int num in greater_than_10) 
 9  :         { 
 10 :             Console.Write(num + ","); 
 11 :         } 
 12 :         Console.WriteLine(); 
 13 :  
 14 :         // C# 2.0 style 
 15 :         greater_than_10 = list.FindAll(delegate(int num) { return num > 10; }); 
 16 :         foreach (int num in greater_than_10) 
 17 :         { 
 18 :             Console.Write(num + ","); 
 19 :         } 
 20 :         Console.WriteLine(); 
 21 :         // C# 3.0 style 
 22 :         greater_than_10 = list.FindAll(num => num > 10); 
 23 :         foreach (int num in greater_than_10) 
 24 :         { 
 25 :             Console.Write(num + ","); 
 26 :         } 
 27 :         Console.WriteLine(); 
 28 :     } 
 29 :     public static bool GreaterThanTen(int num) 
 30 :     { 
 31 :         return num > 10; 
 32 :     } 
 33 : } 
בשורה 5 נגדיר רשימה של המספרים עליה נבצע את המניפולציה.
שורה 7 מדגימה את השימוש ב- Predicate בצורה הרגילה והמסורתית של שימוש ב- Delegate 
Predicate הוא delegate וככזה הוא צריך לקבל יחוס למתודה בעלת חתימה זהה לחתימה שהוא מגדיר, דהיינו שמקבלת אובייקט T ומחזירה bool.
המתודה GreaterThenTen המוגדרת בשורה 29 היא בעלת החתימה המתאימה.
מימוש המתודה הוא, הלכה למעשה, בדיקת הקריטריון שעל פיו FindAll תבצע את הסינון.
בקריטריון במקרה זה הינו גדול מ-10.
בשורה 7 נקרא למתודה FindAll שמקבלת כפרמטר מופע של Predicate אשר מיוחס למתודה GreaterThenTen.
המתודה FindAll תקרא ל-GreaterThenTen בכל פעם שהיא תרצה לבדוק האם איבר של הרשימה עומד או לא עומד בקריטריון שנקבע.
הקוד בשורה 15 עושה בדיוק אותו דבר רק הפעם נגדיר במתודה FindAll מתודה אנונימית במקום השימוש במתודה רגילה.
ובשורה 22 נעשה את מה שכבר עשינו פעמיים בתוכנית רק הפעם תוך שימוש בביטוי למבדה.
החשיבות של Predicate בולטת, לולא הוגדר כחלק מהתשתית היה הרבה יותר מורכב לכתוב מתודה כמו FindAll, צריך היה להגדיר ממשק ולממש את הממשק במחלקה עליה מבצעים מניפולציה, הרבה יותר קוד, הרבה יותר כאב ראש

פלט:
פלט תוכנית הדוגמה PredicateSample01

 
Action<T> Delegate
Action זה שם כולל לסדרה של 17 delegates אשר מקבלים בין 0-16 פרמטרים ואינם מחזירים ערך, Action<T> היא הוותיקה מבין ה-delegates אשר יוצגו במאמר, היא הוצגה ב-.NET Framework 2.0 , שאר ה-delegates במשפחה הוצגו מאוחר יותר:
  1. Action
  2. Action<T>
  3. Action<T1, T2>
  4. Action<T1, T2, T3>
  5. Action<T1, T2, T3, T4>
  6. Action<T1, T2, T3, T4, T5>
  7. Action<T1, T2, T3, T4, T5, T6>
  8. Action<T1, T2, T3, T4, T5, T6, T7>
  9. Action<T1, T2, T3, T4, T5, T6, T7, T8>
  10. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9>
  11. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
  12. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
  13. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
  14. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
  15. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
  16. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
  17. Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
כשמו כן הוא, נועד לביצוע פעולה.
החתימה המלאה של ה-delegate (שמקבל שני פרמטרים לדוגמה) כפי שהוא מוגדר במרחב השמות System:
public delegate void Action<in T1, in T2>(T1 arg1,T2 arg2)
 
הדוגמה הראשונה, ActionSample01,  תדגים שימוש פשוט שלו:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Action<int, int, int, int> sum = (n1, n2, n3, n4) => Console.WriteLine(n1 + n2 + n3 + n4); 
 6  :         sum(11, 22, 33, 44); 
 7  :  
 8  :         string message1 = "The first line of a message"; 
 9  :         string message2 = "The second line of a message"; 
 10 :         string message3 = "The third line of a message"; 
 11 :         Action<string, string, string> print = (s1, s2, s3) => 
 12 :         { 
 13 :             Console.WriteLine("{0}", s1); 
 14 :             Console.WriteLine("{0}", s2); 
 15 :             Console.WriteLine("{0}", s3); 
 16 :         }; 
 17 :         print(message1, message2, message3); 
 18 :     } 
 19 : } 
בשורה 5 מוגדר מופע של Action שמקבל 4 פרמטרים מהטיפוס int, הפעולה שהוא מבצע: הדפסה של סכום ארבעתם.
בשורה 6 הפעלת ה-delegate
בשורה 11 מוגדר מופע נוסף של Action המקבל שלושה מחרוזות, הפעולה: הדפסתם למסך.
בשורה 17– הפעלת ה-delegate.

פלט:
 
פלט תוכנית הדוגמה ActionSample01

הדוגמה השנייה, ActionSample02,  מציגה שימוש מאוד נפוץ ב-Action.
המחלקה List<T> מכילה מתודה בשם ForEach, המתודה יודעת לעבור בצורה סדרתית על כל אחד מהאיברים של הרשימה ולבצע פעולה אחידה על כולם.
החתימה המלאה של המתודה:
public void ForEach(Action<T> action)
ואיך מייצגים את הפעולה המסוימת? באמצעות Action:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         List<String> names = new List<String>() { "Elimelech", "Zrubavel", "Havakuk", "Yerachmiel" }; 
 6  :         names.ForEach(str => Console.WriteLine(str)); 
 7  :  
 8  :         List<Robot> list = new List<Robot> 
 9  :             {  
 10 :                 new Robot { Name = "Wall-E", Id = "DZX17767" },  
 11 :                 new Robot { Name = "Rodriguez", Id = "WRT12" },  
 12 :                 new Robot { Name = "HAL 9000", Id = "DHG167" }, 
 13 :                 new Robot { Name="ASIMO", Id="TT76" }, 
 14 :                 new Robot { Name="Rosie ", Id="HJTP95455" }, 
 15 :                 new Robot { Name=" Optimus Prime ", Id="EKL3" } 
 16 :             }; 
 17 :         list.ForEach(robot => Console.WriteLine("My name is {0}", robot.Name)); 
 18 :     } 
 19 : } 
 20 : class Robot 
 21 : { 
 22 :     public string Name { get; set; } 
 23 :     public string Id { get; set; } 
 24 : } 
תחילה נפעיל את ForEach על רשימה של מחרוזות (שורה 6) ונדפיס אותם.
לאחר מכן ניצור (שורה 8) חבורת רובוטים חביבה שתשמר ברשימה.
בשורה 17נפעיל את ForEach על החבורה ונדפיס את שמם.
בשני המקרים, מתבצעת פעולה, אין לפעולה ערך מוחזר משום ש- ForEach הוגדרה מראש כמקבלת פרמטר מטיפוס Action.
פלט:
פלט תוכנית הדוגמה ActionSample02

 
Func<T> Delegate
Func זה שם כולל לסדרה של 17 delegates אשר מקבלים בין 0-16 פרמטרים ומחזירים ערך, הסדרה הוצגה לראשונה ב-.NET Framework 3.5:
  1. Func<TResult>
  2. Func<T, TResult>
  3. Func<T1, T2, TResult>
  4. Func<T1, T2, T3, TResult>
  5. Func<T1, T2, T3, T4, TResult>
  6. Func<T1, T2, T3, T4, T5, TResult>
  7. Func<T1, T2, T3, T4, T5, T6, TResult>
  8. Func<T1, T2, T3, T4, T5, T6, T7, TResult>
  9. Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>
  10. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>
  11. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>
  12. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>
  13. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>
  14. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>
  15. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>
  16. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>
  17. Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>

הסדרה הזו של ה-delegates מאוד שימושית, רבים הם השימושים המובנים ב-.NET
החתימה המלאה של ה-delegate (שמקבל שני פרמטרים לדוגמה) כפי שהוא מוגדר במרחב השמות System:
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2)

T1 ו-T2 הם הפרמטרים, TResult הנו הערך המוחזר.
שימו לב, תמיד הטיפוס האחרון ברשימה הוא הטיפוס של הערך המוחזר.

 דוגמה ראשונה ופשוטה -
FuncSample01:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Func<double, double> Square = x => x * x; 
 6  :         var result1 = Square(5); 
 7  :         Console.WriteLine(result1); 
 8  :  
 9  :         Func<double, double, double> Mul = (x, y) => x * y; 
 10 :         var result2 = Mul(5, 3); 
 11 :         Console.WriteLine(result2); 
 12 :  
 13 :         Func<double[], double[], double> SumArray = (x, y) => 
 14 :         { 
 15 :             var sum = 0.0; 
 16 :             x.ToList().ForEach(num => sum += num); 
 17 :             y.ToList().ForEach(num => sum += num); 
 18 :             return sum; 
 19 :         }; 
 20 :         double[] arr1 = new double[] { 1.1, 2.2, 3.3, 4.4 }; 
 21 :         double[] arr2 = new double[] { 1.1, 2.2, 3.3 }; 
 22 :         var result3 = SumArray(arr1, arr2); 
 23 :         Console.WriteLine(result3); 
 24 :     } 
 25 : } 
בשורה 5 נגדיר מופע של Func שמקבל פרמטר בודד ומחזיר את ערכו בריבוע.
בשורה 9 נגדיר מופע שמקבל 2 פרמטרים ומחזיר את תוצאת המכפלה.
בשורה 13 המופע של Func מקבל שני מערכים ומחזיר את סכום כל האיברים שלהם תוך שימוש ב-ForEach על מנת לסכם.

פלט:
פלט תוכנית הדוגמה FuncSample01


המחלקות List<T> ו-Array עושות שימוש מסיבי בסדרה הזו של ה-delegates, בדוגמאות הבאות נציג חלק מהשימושים הנרחבים של המחלקות הללו ב-Func, כל מקטעי הקוד שיוצגו להלן מופיעים בדוגמת הקוד FuncSample02:
 דוגמה ראשונה, תחילה נבחן את המתודה Average:
 1  : public static void Method1() 
 2  : { 
 3  :     string[] friends_name = { "Elimelech", "Zrubavel", "Havakuk", "Yerachmiel" }; 
 4  :     double average = friends_name.Average(s => s.Length); 
 5  :     Console.WriteLine("The average string length is {0}.", average); 
 6  : } 
כדי שלא נתבלבל, Average  נראית כמו מתודה רגילה, אבל היא רק נראית כזו כי בפועל היא Extension Method המוגדרת במחלקה Enumerable.
המתודה Average יודעת לחשב ממוצע של ערכי int, מכיוון שבדוגמה המערך הוא של מחרוזות, Average לא מסוגלת לחשב ממוצע מחרוזות (זה גם לא הגיוני למען האמת).
נשתמש ב-Average על מנת לחשב את ממוצע אורך המחרוזות, אולם מכיוון ש-Average לא מכירה את כוונותינו וגם אינה יכולה לנחש זאת אנחנו צריכים לספר לה מה כוונתנו.
נעשה זאת באמצעות Func.
באמצעות Func נעביר לה את אורך המחרוזת, את שאר עבודת החישוב היא כבר תדע לבצע בעצמה.
פלט:
פלט התוכנית FuncSample02
הקוד בשורה 4 משתמש בלמבדה, לחילופין ניתן היה לכתוב את הקוד הבא, הוא אקוויוולנטי לחלוטין:
 1  : public static int MyFunc(string s) 
 2  : { 
 3  :     return s.Length; 
 4  : } 
 5  : private static void Method2() 
 6  : { 
 7  :     string[] friends_name = { "Elimelech", "Zrubavel", "Havakuk", "Yerachmiel" }; 
 8  :     Func<string, int> func = MyFunc; 
 9  :     double average = friends_name.Average(func); 
 10 :     Console.WriteLine("The average string length is {0}.", average); 
 11 : } 
 
שורה 1- הפעם נגדיר מתודה שחתימתה זהה לגרסה של delegate Func המוכתבת על ידי המתודה Average.
המתודה מקבלת פרמטר מהטיפוס string int, המתודה MyFunc מספקת למתודה Average את המידע הנחוץ לה לחישוב ממוצע.
ה- delegate עצמו מוגדר בשורה 8 והוא מיוחס למתודה MyFunc .
בשורה 9 אנו מעבירים למתודה Average את ה-delegate, מכאן Average כבר יודעת לבצע את המוטל עליה.
הערה – אלו שעדיין מתקשים ומתבלבלים בתחביר של למבדה יכולים להפיק תועלת מהשוואה והבנה של שני מקטעי הקוד הללו.

דוגמה שניה, שימוש במתודה OrderBy על מנת לסדר את הערכים במערך או ברשימה:

 1  :  private static void Method3() 
 2  : { 
 3  :     List<String> names = new List<String>() { "Zelda", "Zrubavel", "Elimelech", "Havakuk", "Yerachmiel", "Yona", "Zalman", "Pirhiya", "Fruma" }; 
 4  :     var tmp1 = names.OrderBy(str => str).ToList(); 
 5  :     tmp1.ForEach(str => Console.WriteLine(str)); 
 6  : } 
גם הפעם וכדי שלא נתבלבל, OrderBy  רק נראית כמו מתודה רגילה,  בפועל גם היא Extension Method המוגדרת במחלקה Enumerable.
הפעם Func מעביר למתודה OrderBy את הקריטריון לסידור איברי הרשימה, במקרה דנן, המחרוזת עצמה.
פלט:
פלט התוכנית FuncSample02 - שלב שני

ואם נרצה לסדר את המחרוזות לפי אורך?, נדאג ש-OrderBy תדע זאת:
 1  : private static void Method4() 
 2  : { 
 3  :     List<String> names = new List<String>() { "Zelda", "Zrubavel", "Elimelech", "Havakuk", "Yerachmiel", "Yona", "Zalman", "Pirhiya", "Fruma" }; 
 4  :     var tmp2 = names.OrderBy(str => str.Length).ToList(); 
 5  :     tmp2.ForEach(str => Console.WriteLine(str)); 
 6  : } 
דרך Func נעביר לה קוד למבדה במציין שהפעם הקריטריון הוא אורכה של המחרוזת (שורה 4).
פלט:
פלט תוגנית הדוגמה FuncSample03 - שלב שלישי

הדוגמה הרביעית תדאג לסדר את איברי המערך בסדר יורד:
 1  : private static void Method4() 
 2  : { 
 3  :     List<String> names = new List<String>() { "Zelda", "Zrubavel", "Elimelech", "Havakuk", "Yerachmiel", "Yona", "Zalman", "Pirhiya", "Fruma" }; 
 4  :     var tmp1 = names.OrderByDescending(str => str).ToList(); 
 5  :     tmp1.ForEach(str => Console.WriteLine(str)); 
 6  : } 

בדיוק אותו דבר כמו בדוגמה השלישית .... אבל להיפך.
פלט:
פלט תוכנית הדוגמה FuncSample02 - שלב רביעי


סינון מתבצע באמצעות המתודה Where שגם היא למעשה Extension Method, הדוגמה החמישית מציגה סינון, תדפיס רק את המחרוזות שאורכן עולה על 6 תווים:

 1  : private static void Method5() 
 2  : { 
 3  :     List<String> names = new List<String>() { "Zelda", "Zrubavel", "Elimelech", "Havakuk", "Yerachmiel", "Yona", "Zalman", "Pirhiya", "Fruma" }; 
 4  :     var tmp3 = names.Where(str => str.Length > 6).ToList(); 
 5  :     tmp3.ForEach(str => Console.WriteLine(str)); 
 6  : } 
גם הפעם נעביר את המיון לסינון באמצעות Func.
פלט:
פלט תוכנית הדוגמה FuncSample02 - חלק חמישי 

 
קיבוץ נתונים על פי קריטריון מתבצע על ידי המתודה GroupBy שכמו קודמותיה גם היא Extension Method, הדוגמה השישית מציגה את השימוש בה ואת הדרך בה מעבירים לה את הקריטריון לקיבוץ באמצעות Func, במקרה זה הקריטריון לקיבוץ הוא התו הראשון של השם:

 1  : private static void Method6() 
 2  : { 
 3  :     List<String> names = new List<String>() { "Zelda", "Zrubavel", "Elimelech", "Havakuk", "Yerachmiel", "Yona", "Zalman", "Pirhiya", "Fruma" }; 
 4  :     var tmp1 = names.GroupBy(str => str[0]).ToList(); 
 5  :     tmp1.ForEach(arr => 
 6  :     { 
 7  :         Console.WriteLine("Count = {0}", arr.Count()); 
 8  :         arr.ToList().ForEach(str => Console.WriteLine(str)); 
 9  :         Console.WriteLine("***************"); 
 10 :     }); 
 11 : } 

פלט:
 פלט תוכנית הדוגמה FuncSample02 - חלק שישי

 




כל הזכויות שמורות למחבר ©