How To: Force a switch block to indicate a compile time error when none of the case clauses are satisfied

17 בנובמבר 2008

תגיות:
3 תגובות


בדרך כלל כשאנחנו עושים switch על value כלשהו, אנחנו כותבים קוד גם עבור ה- default


 




  1     private void MyFunction(int value)


  2     {


  3         switch (value)


  4         {


  5             case 1:


  6                 // ..


  7                 break;


  8             case 2:


  9                 // ..


  10                break;


  11            default:


  12                // ..


  13                break;


  14         }


  15    }


 



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




  1 throw new NotImplementedException(value.ToString());



הבעייה, מה קורה כשאנחנו רוצים לקבל Compile Error אם אם ה value לא מתאים לאחד מה case.


כמובן שבמקרה שלנו שה value אינו ידוע מראש, כמו בדוגמא הקודמת שה value הוא int, אי אפשר לבקש Compile Error,


אבל נסתכל על המקרה הבא:



 



    1     public enum DatabaseProviderType


    2     {


    3         SqlServer,


    4         Oracle


    5     }


 



    6     private void MyFunction(DatabaseProviderType value)


    7     {


    8         switch (value)


    9         {


   10             case DatabaseProviderType.SqlServer:


   11                 // ..


   12                 break;


   13             case DatabaseProviderType.Oracle:


   14                 // ..


   15                 break;


   16             default:


   17                 break;


   18         }


   19     }



כשה – value שלנו הוא משתנה מסוג enum, אנחנו יודעים מראש איזה ערכים יכלים להיות ב value, ואנחנו עושים case עבור כל המקרים,


השאלה, מה יקרה כשנוסיף ל- enum עוד ערך, אנחנו רוצים שבכל המקומות שעשו switch על ה enum נקבל שגיאת קומפילציה כדי שנוכל לתקן, ולא שגיאה ב runtime,


כי במקרה הזה אנחנו יודעים בוודאות שאנחנו רוצים שהמשתמשים שעושים switch על ה- enum יממשו case עבור כל מקרה,


אין בעיה שמשתמש יזרוק exception אם הוא לא רוצה באמת לממש, אבל אנחנו רוצים לחייב אותו לכתוב את בלוק ה case.


 


חבר שלי אלכס אבוגוב חשב על רעיון שלדעתי הוא אדיר.


נניח שיש לנו את ה enum של DatabaseProviderType, נכתוב את המתודה הבאה:




   1     public static void Switch(DatabaseProviderType type, Action sqlServer, Action oracle)


   2     {


   3         switch (type)


   4         {


   5             case DatabaseProviderType.SqlServer:


   6                 sqlServer();


   7                 break;


   8             case DatabaseProviderType.Oracle:


   9                 oracle();


   10                break;


   11            default:


   12                throw new NotImplementedException(type.ToString());


   13        }


   14    }



 


מעכשיו, כל מפתח שרוצה לעשות switch על DatabaseProviderType, יכתוב כך:




   1     private void MyFunction(DatabaseProviderType type)


   2     {


   3         Switch(type,


   4             () =>


   5             {


   6                 Console.WriteLine("this block is for SqlServer");


   7             },


   8             () =>


   9             {


   10                Console.WriteLine("this block is for Oracle");


   11            });


   12    }



 בבלוק הקודם יש שימוש ב Lambda Expression,


נניח, שבכל הפרויקטים עשו הרבה פעמים שימוש ב Switch שכתבנו, ועכשיו אנחנו רוצים לתמוך ב Database חדש, מה שצוות התשתיות יעשה, זה להוסיף ל enum את ה type החדש, ישנה את החתימה של המתודה Switch, והופ. כל המקומות שעשו Switch לא יתקמפל, והמתכנתים יצטרכו לממש בכל המקומות את הקוד עבור ה Database החדש, ולא יקרה מצב שבו באחד המקומות שכחו לעדכן, והאפליקציה תתרסק בזמן אמת.


אחרי השינוי הקוד יראה כך:




    1     public enum DatabaseProviderType


    2     {


    3         SqlServer = 1,


    4         Oracle,


    5         SqlCE


    6     }


 



    7     public static void Switch(DatabaseProviderType type, Action sqlServer, Action oracle, Action SqlCe)


    8     {


    9         switch (type)


   10          {


   11             case DatabaseProviderType.SqlServer:


   12                 sqlServer();


   13                 break;


   14             case DatabaseProviderType.Oracle:


   15                 oracle();


   16                 break;


   17             case DatabaseProviderType.SqlCE:


   18                 SqlCe();


   19                 break;


   20             default:


   21                 throw new NotImplementedException(type.ToString());


   22         }


   23     }



כמו שאנחנו רואים, הוספנו ל enum עוד type, שינינו את החתימה של המתודה Switch, וכל מי שהשתמש בה, יהיה חייב להוסיף מימוש עבור ה type החדש.


 


אני חושב שהגיע הזמן שנוכל להוסיף איזה Attribute על enum שמשמעותו, שכשעושים switch על ה enum, חייבים לממש את הכל, או שמקבלים שגיאת קומפילציה,


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


 


 


 


 


 

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

3 תגובות

  1. Kim17 בנובמבר 2008 ב 8:12

    "נניח, שבכל הפרויקטים עשו הרבה פעמים שימוש ב Switch שכתבנו"
    I would consider that a code smell.

    I don't have the whole context of your project, but I think I would prefer polymorphism and the command pattern over that funky Switch().

    הגב
  2. Arielr17 בנובמבר 2008 ב 9:03

    לא יהיה יותר קל לכתוב חוק של FXCOP ולסגור עניין?

    הגב
  3. Shlomo17 בנובמבר 2008 ב 11:14

    fxcop זה גם אפשרות, אבל אני חושב שלכתוב fxcop האם בתוך המתודה עושים switch על enum ואם כן על כל האפשריות, זה לא הכי פשוט שבעולם.

    אשמח אם תשלח לי לינק.

    הגב