יום שישי, 11 באוקטובר 2013

Linq Deep Dive - Where

כאשר חלק מהמתכנתים מביטים לראשונה על שאילתות Linq , הם נראות להם מאיימות.

בפוסט זה אסביר איך בעצם ממומשים מתודות 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 שמטרתה לבדוק אם המספר הוא אי-זוגי:


/// <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;
}

וכמובן ניצור delegate מתאים:

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 פעולות:

  1. לשנות את הפונקציה ל  Extension method- כדי להפוך את הפונקציה ל Extension method פשוט נוסיף את המילה this לפני הארגומנט הראשון, ונעביר את הפונקציה ל class סטטי.
  2. לבצע 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:

result= nums.Filter( item =>  item % 2 == 0);

שזה בדיוק כמו:

result= nums.Where( item =>  item % 2 == 0);


תהנו!

יום חמישי, 3 באוקטובר 2013

Delegate, Action, Func ומה שבינהם

חבר בעבודה שאל אותי השבוע: מה זה Func?

אז בשביל להסביר לו, עניתי לו שזה פשוט צורה יותר קלה להגדרת Delegate.

אז נתחיל מההתחלה: מה זה Delegate?

 Delegate זה אובייקט Reference שמצביע לפונקציה.היתרון בשימוש ב Delegate זה שצד שלישי יכול להגדיר איזה פונקציה הוא בדיוק רוצה שתופעל, והמימוש שלנו לא יהיה תלוי בכך.

הגדרת Delegate:

הגדרת ה Delegate תהיה תחת ה namespace שלנו, ולא תחת Class מסוים. לדוגמא:

delegate void PrintDelegate(string st);

מה הגדרנו כאן: הגדרנו שה Delegate שלנו יכול להצביע לכל פונקציה שהחתימה שלה: מחזירה Void ומקבלת String.

נאמר שיש לנו בתוכנית את שתי הפונקציות הנ"ל:

static void PrintOne(string st)
        {
            System.Console.WriteLine("{0} is in PrintOne Function", st);
        }

        static void PrintTwo(string st)
        {
            System.Console.WriteLine("{0} is in PrintTwo Function", st);
        }

לאחר שנריץ את הקוד הבא:

static void Main(string[] args)
        {
            PrintDelegate p = new PrintDelegate(PrintOne);
            
            p("Eyal");
        }

נקבל:
Eyal is in PrintOne Function

בשורה מספר 3 הגדרנו שה Delegate שלנו יצביע לפונקציה PrintOne, ובשורה 5 פשוט קראנו לDelegate שלנו בדיוק כמו שהיינו קוראים אם היינו קוראים לפונקציה.

Multycast Delegate

Delegate יכול להצביע גם על מספר פונקציות (נקרא Multycast Delegate). וכאשר "מפעילים" את ה Delegate, הוא יקרא לפונקציות בסדר שהם נרשמו. את הרישום נעשה באמצעות אופרטור =+

למשל:

static void Main(string[] args)
        {
            PrintDelegate p = new PrintDelegate(PrintOne);
            p += PrintTwo;
            p("Eyal");
        }


ונקבל:
Eyal is in PrintOne Function
Eyal is in PrintTwo Function

Closure

שימוש נפוץ ל Delegate הוא ביצוע Closure, כלומר העברת פונקציה כפרמטר.

נניח ויש לנו את הפונקציה הנ"ל:

static void MyPrintMethod(PrintDelegate p)
        {
            p("MyPrintMethod");
        }

הפונקציה מקבלת כפרמטר Delegate ומפעילה אותו.

ואז בתכנית שלנו פשוט נוכל לאתחל את ה Delegate עם הפונקציה שאנו רוצים לשלוח, וכך להפעיל את הפונקציה:

MyPrintMethod(new PrintDelegate(PrintTwo));

ונקבל:
MyPrintMethod is in PrintTwo Function

Generic Delegate

אנחנו יכולים גם להגדיר שה Delegate שלנו יהיה גנרי, ואז לא נצטרך לרשום Delegate שונים עבור פונקציות שמקבלות Type שונים:

לשם ההדגמה נניח ויש לנו פונקציית הדפסה חדשה:


static void PrintThree(int num)
        {
            System.Console.WriteLine("{0} is in PrintThree Function", num);
        }

עכשיו תחת ה namespace נגדיר את ה Delegate הגנרי הבא:

delegate void PrintGenericDelegate(T t);

וכעת נוכל להשתמש עם אותו ה Delegate שיצביע על פונקציות עם חתימות שונות:



PrintGenericDelegate<string> pgs = new PrintGenericDelegate<string>(PrintTwo);

 pgs("Eyal");

 PrintGenericDelegate<int> pgi = new PrintGenericDelegate<int>(PrintThree);

 pgi(111);
ונקבל:
Eyal is in PrintTwo Function
111 is in PrintThree Function

<Action<T

יופי, אז ראינו שימושים שונים ל Delegate.
אבל זה קצת מסורבל, לא?

החל מ NET 2.0 קיבלנו את Action, שזהו פשוט סוג של Delegate, שיודע לקבל עד 16 פרמטרים, ומחזיר void. השימוש ב Action מקל לנו על כתיבת הקוד.

תראו איזה פשוט נהיה השימוש ב Delegate, באמצעות Action:
אין צורך להגדיר Delegate ב namespace, אלא פשוט יוצרים Action, ונותנים לו הצבעה לפונקציה:


Action<string> MyAction = PrintTwo;

 MyAction("action");

ונקבל:
action is in PrintTwo Function

ונראה איזה פשוט זה לשלוח פונקציה כפרמטר באמצעות Action (ביצוע Closure):

נניח ויש לנו את הפונקציה:

static void MyPrintMethodAction(Action<string> action)
        {
            action("MyPrintMethodAction");
        }

נפעיל את הקריאה ע"י:

Action<string> MyAction = PrintTwo;

MyPrintMethodAction(MyAction);

ונקבל:
MyPrintMethodAction is in PrintTwo Function

<Func<T,Result

ולשאלתנו, מה זה Func? זה פשוט כל מה ש Action, רק ש Func מחזיר פרמטר.

יש לנו לדוגמא את הפונקציה הפשוטה הבאה שמקבלת פרמטר מסוג int, ומחזירה int:

static int MultiplyTwice(int num)
        {
            return num * 2;
        }


נשתמש עם ה Delegate הבא:

Func<int,int> MyFunc = MultiplyTwice;

Console.WriteLine(MyFunc(4));


ונקבל: 8

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

תהנו!!