יום שני, 25 בפברואר 2013

אני לא מכיר מה זה Switch - Case

אני כן מכיר מה זה. אבל אנחנו צריכים לבוא בגישה שאסור לנו להשתמש בזה. למה?

  1. קשה לתחזוקה
  2. לא קריא
  3. כאשר יש המון Case אז נקבל ביצועים נמוכים
וכשאני מתכוון לא להשתמש ב Switch-Case, אני מתכוון גם לא להשתמש במשפטי If מקוננים. את ההבדל בביצועים בין switch ל case אפשר לקרוא כאן

אני אראה לכם למה אני מתכוון.

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

קודם ניצור Enum עם סוגי המכוניות:

 enum CarEnum{
            BMW,VW,MERCEDES
        }

לאחר מכן ניצור מחלקה שיש בה פונקציה מתאימה לכל סוג רכב:

  public class CarHandler
        {
            public void handleBMW()
            {
                System.Console.WriteLine("BMW");
            }

            public void handleVW()
            {
                System.Console.WriteLine("VW");
            }

            public void handleMERCEDES()
            {
                System.Console.WriteLine("MERCEDES");
            }

        }

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

 public class CarShop
        {
            private CarHandler carHandler = new CarHandler();

            public void handleCar(CarEnum car)
            {
                switch (car)
                {
                    case CarEnum.BMW :
                        carHandler.handleBMW();
                        break;
                    case CarEnum.MERCEDES:
                        carHandler.handleMERCEDES();
                        break;
                    case CarEnum.VW:
                        carHandler.handleVW();
                        break;
                    default: throw new Exception("unknown Car");
                }
            }
        }

עכשיו תארו לכם שאתם צריכים לתחזק את המערכת, ולהוסיף עוד ועוד מכוניות.

  1. אתם צריכים להוסיף משפט Case
  2. אתם צריכים להוסיף ערך ל enum
  3. אתם צריכים לכתוב פונקציה בתוך מחלקת CarHandler
תחשבו איזה ארוך ולא קריא יהיה קטע ה Switch  עבור כל סוגי המכוניות...

אז מה עושים?

למדתי אצל Evgeny Borisov את השימוש הנכון :

מה שנעשה זה ניצור Enum "מיוחד" שמדמה את ה Enum שאפשר ליצור בJAVA. ה Enum יכיל בנאי, ועבור כל ערך חדש (כל מכונית חדשה) הבנאי יופעל, ויכניס לתוך LIST את רשימת כל הערכים עם הצבעה לפונקציות המתאימות.

הוא יראה כך: (CarEnum)

public class CarEnum
        {

            public class CarEnum
        {

            private String enumName;
            private CarHandler handler;
            private int dbCode;

            private static List<CarEnum> enums = new List<CarEnum>(5);

            static CarEnum()
            {
                enums.Add(new CarEnum("BMW", new BMWHandler(), 1));
                enums.Add(new CarEnum("VW", new VWHandler(), 2));
                enums.Add(new CarEnum("MERCEDES", new MERCEDESHandler(), 3));
            }

            private CarEnum(String enumName, CarHandler handler, int dbCode)
            {
                this.enumName = enumName;
                this.handler = handler;
                this.dbCode = dbCode;
            }

            public CarHandler getHandler()
            {
                return handler;
            }

            public static CarEnum getEnumByCode(int code)
            {

                foreach (var carEnum in enums)
                {
                    if (carEnum.dbCode == code)
                    {
                        return carEnum;
                    }
                }

                throw new Exception("wrong code " + code);

            }

            public static CarEnum getEnumByName(string name)
            {

                foreach (var carEnum in enums)
                {
                    if (carEnum.enumName == name)
                    {
                        return carEnum;
                    }
                }

                throw new Exception("wrong name " + name);

            }
        }

כעת ניצור interface שכל מכונית תצטרך לממש אותו:


public interface CarHandler
        {
            void handleCar();
        }
כל מכונית תממש את ה interface:

 public class BMWHandler : CarHandler
        {
            public void handleCar()
            {
                System.Console.WriteLine("BMW");
            }
        }

        public class VWHandler : CarHandler
        {

            public void handleCar()
            {
                System.Console.WriteLine("VW");
            }
        }

        public class MERCEDESHandler : CarHandler
        {

            public void handleCar()
            {
                System.Console.WriteLine("MERCEDES");
            }
        }

ועכשיו לחלק המעניין: זוכרים את ה CarShop שהיה לנו במקרה של Switch-Case? הארוך עם הרבה Case?
תראו מה נקבל עכשיו:

public class CarShop
 {
      public void handleCar(CarEnum car)
      {
          car.getHandler().handleCar();
      }
 }
והנה דוגמא לצורה שנפעיל את הקוד:
            //create a new shop
            CarShop shop = new CarShop();

            //get the correct enum
            CarEnum carEnum = CarEnum.getEnumByName("BMW");

            //handle the right handler
            shop.handleCar(carEnum);

היתרונות של הקוד הנ"ל:
 כאשר נצטרך לתחזק את הקוד ולהוסיף מכונית:
  1. נוסיף שורת קוד אחת ל enum (לבנאי הסטטי)
  2. נכתוב פונקציה למכונית החדשה שממשת את CarHandler
וגם הקוד יהיה יותר קריא, ללא שום Case

אז תפסיקו להשתמש ב Switch-Case!

את קובץ ה cs אפשר להוריד מכאן

יום חמישי, 21 בפברואר 2013

.NET Collections - Best Practise - Part 2

בפוסט הקודם, ראינו כמה דרכים נכונות להשתמש ב List.

החסרון של שימוש ב List זה שזמן הכנסת ,הוצאת ,מחיקת איבר מהרשימה זה (O(n. אז במה אנחנו צריכים להשתמש אם אנחנו צריכים הכנסה, הוצאה, מחיקה מהירה?

<LinkedList<T

כל איבר ברשימה מצביע לאיבר שלפניו ואחריו.
לכן זה אומר שפעולות הכנסה, הוצאה, מחיקה יהיו ממש מהירות (O(1.

עוד על הבדלים בין LinkedList לבין List אפשר לקרוא כאן.

החסרון ברשימה מקושרת זה חיפוש. אז מה עושים אם אנחנו רוצים לבצע חיפוש? 

<Dictionery<Tkey,TValue

 הביצועים תלויים בכמה מהר מבוצע ()key.GetHashCode : הפונקציית "האש" צריכה להיות בעלת פיזור אחיד. ואם אנחנו משתמשים באובייקטים שאנחנו יצרנו, אז אנחנו אחראים לפונקציית "האש", ואז אנחנו צריכים לחשוב איך לכתוב את הפונקציה בצורה המיטבית, כך שתתן פיזור אחיד.
בשביל למצוא Key מסויים, ה Dictionery קורא ל Equals. וצריך לזכור שכאשר מדובר באובייקט שיצרנו, אנחנו נהיה חייבים לממש את Equals, וגם את ()GetHashCode. (אם לא נממש את שניהם אז נקבל שגיאת קומפילציה..

חסרון שקיים זה שאנחנו יכולים לשמור רק Value אחד עבור כל Key. אז מה עושים אם אנחנו צריכים לשמור כמה ערכים עבור כל Key?

<Lookup<Tkey,TElement

טוב, אז Lookup  זה לא באמת ממשפחת ה" Collections". הוא תחת System.Linq . תפקידו זה למפות Key לקבוצה של Values. 
השימוש בו הוא על ידי ToLookup שזה בעצם Extension Method.  
ToLookup דורש delegate מסוג <Func<TSource,Tkey.  נשמע קצת מסובך? אז לא.. דוגמא:
 // Create a Lookup to organize the persons. Use the first name of object Person as a key
    // Iterate over a list of Persons 
    Lookup<string, string> lookup = (Lookup<string, string>)persons.ToLookup(p => p.FirstName,
                                                    p => p.FamilyName);

    // Iterate through each IGrouping in the Lookup and output the contents. 
    foreach (IGrouping<string, string> personGroup in lookup)
    {
        // Print the key value of the IGrouping.
        Console.WriteLine(personGroup.Key);
        // Iterate through each value in the IGrouping and print its value. 
        foreach (string str in personGroup)
            Console.WriteLine(" {0}", str);
    }

Sorted collections

אילו סוגים יש לנו:


מתי נשתמש?
כמובן כאשר נרצה שה Collection שלנו ימויין על פי מפתח כלשהו. אנחנו כמובן צריכים להעביר IComparer בבנאי כדי שיהיה אפשר לדעת על פי מה למיין..
כמובן שצריך לעשות שימוש מושכל מאוד בשימוש ב Collections הללו, כי מבחינת ביצועים השימוש יכול להיות יקר מאוד...

SortedList צורך פחות זיכרון מ SortedDictionery
לSortedDictionery כמובן יש ביצועים יותר טובים עבור הכנסה והוצאה.


Concurrent collections

אילו סוגים יש לנו:

 כל ה Collections הנ"ל הם Thread Safty. הם ממשים את IProducerConsumerCollections, שזה בעצם נותן לנו לדוגמא את הפונקציות הבאות:
  • ()TryAdd- שבעצם מנסה להוסיף אובייקט, ומחזיר לנו ערך בוליאני האם הצליח
  • ()TryTake- שבעצם מנסה להסיר אובייקט, ומחזיר לנו ערך בוליאני האם הצליח (את האובייקט עצמו נקבל ב out)

אז מקווה שלמדתם כמה דברים חדשים על Collections.


יום שני, 11 בפברואר 2013

img onclick event is not triggered in Explorer

היום במשך כשעתיים "התפסטנתי" על דבר כל כך מעצבן, שהפתרון שלו נורא פשוט.

הדבר נורא פשוט:
הייתי צריך להוסיף תמונה לדף HTML, וכאשר המשתמש לוחץ עליה, מופעלת פונקציית JavaScript.
בclient כתבתי:
<img id="eyal" onclick="doSomething()" src=".."/>

וכצפוי, בדפדפן Chrome זה עבד, וכמובן ב Explorer זה לא.. (משום מה כל הבעיות קורות באקספלורר)

הפתרון הוא לעטוף בתגית <a>  ולהעלים את ה border שהיא יוצרת:

<a href="javascript:doSomthing()" ><img id="eyal"  style="border-line:0px" src=".."/></a>

תהנו!

.NET Collections - Best Practise - Part 1


גם אם אתם משתמשים ב Collections, או חושבים שאתם יודעים הרבה על Collections,  אני מבטיח לכם שעד סוף פוסט זה כולכם תלמדו משהו חדש.

למה בכלל אנחנו צריכים להשתמש נכון ב Collections?

התשובה זה ביצועים. אם נדע להשתמש ב Collections שלנו בצורה מושכלת ונדע להוציא מהם את הביצועים המקסימליים אז נוכל "לבזבז" ביצועים על אלמנטים אחרים בקוד שהם "Nice to have", או למשל קטעי קוד שאנחנו מלכתחילה כותבים עם ביצועים פחותים כדי שהם יהיו Maintainable.

נתחיל:

List<T> - Creation

הבנאי הדיפולטיבי יוצר רשימה ריקה (capacity==0). הוספת האלמנט הראשון יוצר ברשימה קיבולת של 4 אלמנטים. כאשר נוסיף את האלמנט החמישי, הקיבולת תגדל פי 2 (כלומר ל 8). בהוספת האלמנט התשיעי, הקיבולת תגדל פי 2 (ל 16) וכך הלאה. (יכול להיות לא יעיל מבחינת שימוש בזכרון..)
הגדלת המקום מתבצעת ע"י Array.Copy- זוהי פעולה מאוד לא יעילה ויש להשתמש איתה כמה שפחות.
לכן:
אם אנחנו יודעים מה הולך להיות הגודל של המערך אז יש להשתמש ב:
List<T> list=new  List<T>(10);

שזה גם ימנע את פעולת Array.Copy, וגם יסדר את "קצב הגדילה" של הרשימה במידה ויתווספו יותר אלמנטים (בדוגמא שלנו יותר מ 10). יש לשים לב שגם כאן קצב הגדילה יהיה אקספוננציאלי.. (10,20,40,80)

List<T> - Collection Initializers

כאשר אנחנו מאתחלים את הרשימה עם ערכים:
List<int> list=new  List<int>(){1,2,3,4,5};

פעולה זאת לא מתורגמת ישירות ל-IL. הקומפילר הופך זאת ל Add.
אז שוב יש לנו את הבעיה ממקודם: האיבר הראשון יוצר מקום ל4 אלמנטים, ה-5 ל 8 אלמנטים,האיבר התשיעי ל 16 אלמנטים וכך הלאה..
הפתרון הוא כמו ממקודם- הגדרת גודל הרשימה:
List<int> list=new  List<int>(10){1,2,3,4,5};

List<T> - Adding Elements

תנסו להשתמש כמה שפחות בפונקציה Add בתוך לולאה. כאשר מוסיפים איברים בתוך לולאה אנחנו נתקל בדיוק בבעיות שתיארנו ממקודם.
לכן אנחנו צריכים להשתמש ב AddRange שמאפשר להוסיף מספר אלמנטים יחד:
list.AddRange(new int[] {1,2,3,4});

לתוך הפונקציה AddRange אפשר להוסיף כל IEnumerable Collection.

להלן גרף המראה את ההבדלים בביצועים בין השימוש ב Add לבין השימוש ב AddRange:



List<T> - Removing Elements

אנחנו מכירים 2 שיטות להסרת איבר ממערך:
By index: list.RemoveAt(6);
By Reference: list.Remove(item);

אנחנו צריכים להעדיף להשתמש בהסרה ע"י אינדקס. (למרות שזה לפעמים יותר קשה לנו לביצוע כמתכנתים).
למה?
הסרה ע"י רפרנס מחפשת את האיבר ברשימה ומקבלת את האינדקס שלו ע"י שימוש במתודה IndexOf.
IndexOf בודקת אם IEquatable ממומש. אם לא אז היא תשתמש ב Equals  הדיפולטיבי שיכול ליצור לנו בעיות אם לא מדובר ב Value Type. כל הפעולות שתיארתי עכשיו גם לוקחות המון זמן...

להלן גרף המראה את ההבדלים בביצועים בין השימוש ב Remove לבין השימוש ב RemoveAt:



List<T> - Sorting

אלגוריתם המיון משתמש ב QuickSort. בסוג מיון זה במצב האופטימלי זמן הריצה יהיה n log n. זהו גם הזמן הממוצע.
אבל במצב הגרוע זמן הריצה (זמן המיון) יהיה n^2. 
אפשר לראות את ההבדלים בזמני הריצה:


מהו המצב הגרוע? לא תאמינו, אבל המצב הגרוע הוא כאשר הרשימה כבר ממויינת! (לא אפרט למה, תאמינו לי..) ולא מספיק על זה, המצב הגרוע הוא גם המצב הממוצע! בממוצע, אנחנו נקבל רשימה שהיא כבר ממויינת.
אז איך מונעים את המקרה הגרוע:
1) לוקחים איבר מסוף הרשימה
2) לוקחים איבר מתחילת הרשימה
3)לוקחים איבר מאמצע הרשימה
4)מחשבים את הערך החציוני- median
 ואז הערך שמצאנו ב(4) יהיה ה Pivot במיון שלנו.

למה אני מספר את כל זה??
כי כל מפתח ממוצע היה אומר ש"המפתחים של מייקרוסופט כבר דאגו לזה". אז זהו שלא!! אם פותחים את הקוד של ה QuickSort ע"י Reflector, אפשר להתבונן איך מתכנתי מייקרוסופט מימשו את הפונקציה. הם עשו "קיצור דרך" וחישבו את כל השלבים הנ"ל חוץ מהוצאת האיבר מאמצע הרשימה (סעיף 3). ב80% מהמקרים זה יעבוד טוב, אך בשאר ה 20% זה לא יעבוד...(עוד אפשר לקרוא כאן)

אז מה נעשה?
אפשרות אחת זה לכתוב פונקציית מיון בעצמנו
אפשרות שנייה זה לבדוק את הביצועים שלנו בזמן שאנחנו קוראים ל Sort. אם הביצועים לא כמו שציפינו, אז אפשר לעשות כמה מניפולציות על הרשימה ואז לשלוח אותה ל Sort.

בפוסט זה התמקדתי בעיקר ב List. בפוסט הבא אני אציג שימושים נכונים ב Collections אחרים.

את הדברים למדתי מהרצאה מעולה של Gary Short