Netwise English
אודות
הפילוסופיה שלנו
חדשות ואירועים
הלקוחות שלנו
ספריית מאמרים
Netwise Experts
ארכיון ניוזלטר
דרושים
צור קשר
הוראות הגעה
 
ספריית מאמרים לספריית מאמרים  לניוזלטר Netwise
Generics לעומק – חלק ב'
המאמר "Generics לעומק" נכתב במקור על ידי ג' אמברוס ליטל (2004) ותורגם ועובד על ידי אייל סקיבא, ראש צוות פיתוח מערכות אינטרנט ארגוניות בחברת Netwise

קישור למאמר המקורי: http://www.15seconds.com/issue/040525.htm
לקריאת חלק א' של המאמר: Generics לעומק – חלק א'
תקציר המאמר:
מפתחים רבים מתקשים להבין את היכולת החדשה שנקראת Generics, שהתווספה בגרסה החדשה של NET 2. בחלק ב' של המאמר נבחן אספקט חשוב במבנים כלליים הנקרא אילוצים - תכונה של Generics, שמאפשרת ליוצר המבנה הכללי להגדיר אילו סוגים של מבנים יכולים להתקבל כארגומנטים חוקיים למבנה הכללי. כמו כן תמצאו במאמר דוגמאות שמשתמשות ביכולות ה-generics.
לאחר שלמדנו כיצד ליצור ולהשתמש במבנים כלליים, אנו צריכים לבחון עוד אספקט חשוב במבנים כלליים הנקרא – אילוצים (constraints). אילוץ הינו תכונה של Generics, שמאפשרת ליוצר המבנה הכללי להגדיר אילו סוגים של מבנים יכולים להתקבל כארגומנטים חוקיים למבנה הכללי. שימוש באילוצים נועד לפתור בעיה של שימוש בפרמטרים מובנים בתוך מבנה כללי. דוגמה לכך ניתן לראות ברישום 4. נראה טוב, נכון? כל עוד המבנה המועבר ב-T יכול להיווצר על ידי "new", וכל עוד T מכיל שיגרה שנקראת Contains שמקבלת מחרוזת, זה ירוץ נהדר.
רישום 4 – ללא אילוצים
ללא אילוצים
אבל איך אנו יכולים להיות בטוחים שהמפתח משתמש ב-MyClass בצורה הנכונה ומעביר את הפרמטרים מהסוג שנתמך על ידי הפונקציות הפנימיות של המבנה? האילוצים באים לפתור בדיוק בעיה זו. ברישום 5, אנו רואים את אותו קוד כמו ברישום 4, פחות או יותר, אבל הוספנו אילוצים על מנת לוודא שהקוד שלנו לא ייפול בזמן ריצה מפני שהמהדר לא יאפשר לצרכנים של MyClass להעביר כ-T דברים שאינם נתמכים לפי האילוצים.
האילוץ "new" מגדיר למבנה T כי הסוג שיועבר אליו צריך לתמוך באפשרות להיווצר על ידי מילת המפתח "new" וההגדרה של אילוץ זה צריכה להיות מוגדרת בסוף עבור C#. האילוץ השני מגדיר שהסוג T שמועבר למבנה צריך ליישם את הממשק IList, כלומר צריך לממש את השגרה Contains שמקבלת פרמטר מחרוזת. שימו לב ש C# מאפשרת לציין אילוצים עבור כל פרמטר במבנה על ידי שימוש במילת המפתח "where", ואילו וב-VB.NET יש להשתמש במילת המפתח "As" אחרי כל פרמטר.
רישום 5 – כולל אילוצים
כולל אילוצים
באפשרותכם להגדיר מבנה בסיס אחד, כל מספר של ממשקים, ומילת המפתח "new" עבור אילוצים למבנה כללי או שיגרה. בנקודה הזאת, אתם בטח תוהים האם יש צורך להשתמש רק באות אחת ראשית כמזהה פרמטרים של מבנה. זו למעשה המלצה של מיקרוסופט, אשר קרוב לוודאי תוטמע בקווים המנחים של העיצוב. שיקול הדעת היה שהאלמנטים המיוחדים הללו יודגשו כך, שהמפתח יבדיל בנקל בין פרמטר מבנה לבין פרמטרים "רגילים" במבנים.
שגרות כלליות וממשקי מבנה
כפי שהזכרנו קודם, ניתן ליצור מבנים כלליים מסוגים נוספים וניתן גם ליצור שגרות כלליות שמקבלות פרמטרים כלליים. שגרות כלליות שימושיות בהרבה מצבים, במיוחד היכן שצריך שהפונקציה תחזיר מבנה מסוג מסוים. מה שיפה בכך הוא שאתם יכולים להשתמש בפרמטר(ים) כללי כערך המוחזר, גם להשתמש כפרמטר כללי שמוכנס לשיגרה וגם בתוך השיגרה עבור משתנים מקומיים.
דוגמה לתחביר ראינו כבר ברישום 1 (ראו חלק א' של המאמר), אבל בדוגמה ברישום 5 אנו מציגים את כל שלושת הדרכים ששיגרה כללית יכולה להשתמש במשתנים כלליים. שימו לב כיצד אנו משתמשים בפרמטר כללי כערך החזרה של השיגרה, לצורת ה"input" כפרמטר כללי נכנס, לצורת ההגדרה של המשתנה הכללי
"tempVar".
רישום 6 – שגרות כלליות
שגרות כלליות
תכונה שימושית נוספת של שגרות כלליות אלו היא "type interface" – במילים פשוטות זו היכולת של התוכנה בזמן ריצה להסיק מהו סוג פרמטר על בסיס הפרמטר שמועבר לשגרה. לדוגמה, ברישום 6, אם היינו מעבירים integer ל"input" כפרמטר לשגרה, רק בזמן הריצה הוא היה מתרגם את פרמטר "T"
ל int32, כך שאנו לא חייבים למעשה לציין איזה סוג מועבר לשיגרה. זה ההבדל בין רישום 7 ורישום 8.
רישום 7 – שימוש בשגרה כללית ללא Type Inference
שימוש בשגרה כללית ללא Type Inference
ברישום 7 אנו רואים שמצוין במפורש סוג הפרמטר הכללי שמועבר לשגרה כ-int32. בכל אופן, כיוון שבזמן הריצה יש אפשרות להניח בקלות מהו סוג הפרמטר שאנו מעבירים לפרמטר "input" (לדוגמה, 55), ולכן לא חייבים לציין במפורש מהו סוג הפרמטר ואנו יכולים להשמיט מידע זה, כמודגם ברישום 8.
רישום 8 – שימוש בשיגרה כללית עם Type Inference
שימוש בשיגרה כללית עם Type Inference
שימו לב שישנם מקרים בהם בזמן הריצה לא ניתן להניח מהו סוג הפרמטר. במקרים שבהם ישנם מספר פרמטרים כלליים (שבהגדרה אמורים להיות מאותו סוג T) שנשלחים לשגרה וכל אחד מסוג שונה (באותה קריאה), בזמן הריצה לא יהיה ניתן להסיק את סוגו האמיתי של הפרמטר הכללי. דוגמה לסיטואציה זו מוצגת ברישום 9.
רישום 9 – בעיות עם Inference
 בעיות עם Inference
בקריאה הראשונה לשגרה ברישום 9, לא ניתן בזמן הריצה להניח את סוג הפרמטר המועבר כ-"T" מפני ששני סוגי הפרמטרים שהועברו לשגרה היו מסוגים שונים. בדוגמה, הם Int32 ומחרוזת. על מנת למנוע בלבול אנו חייבים לציין במפורש מהו סוג הפרמטרים שמועבר לשגרה הכללית, כפי שמוצג בקריאה השנייה על ידי שימוש ב- system.object.
הערות לגבי Generics ב NET.
מגבלות קוד כללי
עכשיו, לאחר שכיסינו את רוב התחביר והמינוחים של Generics, אנו צריכים לשקול את ההגבלות של ה-CLR בהפעלת מנגנוני ה Generics.
מאחר שGenerics ללא אילוצים מוגדר על ידי המהדר כ-system.object, אין באפשרותכם להשתמש בתכונות שלא זמינות עבור system.object ואף אינכם יכולים להשתמש באופרטורים או בקינון מחלקות. לעומת זאת באפשרותכם להשתמש באילוצים על מנת להסיר מגבלות אלו. בכל אופן, עדיין לא תהיה אפשרות גישה לפרמטרים סטטיים או למחלקות מקוננות ללא ביצוע המרה (casting). כמו כן לא ניתן להשתמש בפעולות אריתמטיות אם לא הוגדרו אילוצים שהמבנה הבסיסי תומך בהם. לבסוף, לא ניתן להשתמש ב-Generics עצמם כארגומנטים למבנה, אבל, כמודגם, ניתן להשתמש במבנים כלליים בבנאים של המחלקות.
Generics ב-CLR (מתקדם)
עבור אלו מכם שמעוניינים לדעת כיצד ה-CLR למעשה מטפל ב-Generics בזיכרון, עליכם לדעת כי ערך מסוג מבנה מטופל אחרת מהפנייה למבנה. כל מבנה כללי נוצר עבור סוג ייחודי. לאחר מכן בכל שימוש במבנה מאותו סוג יש שימוש חוזר בהגדרת המבנה של אותו סוג. דבר זה כמובן משפר את הביצועים, כיוון שלא צריך עבור כל הגדרה ולבצע עבודת פענוח של המבנה הכללי. בשני המקרים, ניתן להשתמש בהשתקפות על מנת לקבל מידע על המחלקה בזמן ריצה.
VB.NET
מעודד לדעת שצוות הפיתוח של VB.NET עבד כדי לספק תמיכה עבור generics. מאמר זה מספק דוגמאות מקבילות עבור C# ו VB.NET וברוב המקרים אכן יש הקבלה מלאה.
מה עם תבניות C++?
שאלה שגרתית נוספת שנשאלת לעיתים תכופות היא מה ההבדלים בין generics ו-C++ templates? שניהם מספקים מבנים פרמטריאלים, אבל ב-NET. השתדלו ליצור מצב שיהיה נוח להשתמש במנגנון ולכן פגעו מעט בגמישות. להלן רשימה קצרה של מאפיינים שלא זמינים ב-NET generics. ושזמינים בתבניות C++.
הגדרת פרמטרים כלליים מסוג מסוים, כמו template <int i> {}
אין אפשרות להגדיר במפורש או באופן חלקי הסגות למחלקה או לפרמטר
לא ניתן להשתמש במחלקות כלליות כמחלקות בסיס למחלקות אחרות
אין אפשרות להגדיר ברירות מחדל לפרמטרים כלליים
לא ניתן להשתמש בGenerics כפרמטר, למרות שבנאים ניתן להגדיר
NET. מציע יתרון אחד גדול לעומת התבניות ב- C++ המאפשר לכתוב קוד שלא נתמך על ידי התבניות וניתן, למשל, להעביר פרמטרים שאינם נתמכים. NET. דורש לכתוב את הקוד המטפל במשתנים הכלליים בצורה מסוימת, כך שיתאים לכל סוג שמוגדר על ידי האילוצים. למשל ב-C++ ניתן לכתוב שגרה שמשתמשת בפעולות + ו – על אובייקטים שמועברים בתור הפרמטרים, משהו שכמובן יגרום לשגיאה.
NET. לא מאפשר זאת על ידי שימוש באילוצים המתאימים.
השוואה מול Java
Java תתמוך במנגנון דומה ל-generics בגרסה הבאה. השוני בשיטת העבודה בין השפות הוא שב-Java המנוע הראשי (JVM) לא ישונה. כלומר, על מנת לספק את התמיכה כל האובייקטים יוסבו למכנה הנמוך ביותר שהינו אובייקט. כיוון ש-Java מבצע הסבה זו, לא יהיה ניתן לקבל מידע על סוגו של המשתנה.
לסיכום
למאמר זה מצורפות שתי דוגמאות שמשתמשות ביכולות generics. הדוגמה הראשונה היא יחסית אקדמית, המנסה להדגים כמה מיכולות הgenerics. תוכלו למצוא בה שימוש בפרמטרים כלליים שמייצגים סוגים שונים של אנשים וזיהוי סוגי האנשים בחברה. לאחר זיהוי סוג האיש יש זיהוי של עבודתו של האיש בחברה. באפשרותכם להרחיב דוגמה זו בקלות לכל שימוש בו צריך להבחין בין סוגי מחלקות (במקרה של הדוגמה person) ולטפל בכל סוג באופן שונה.
בדוגמה השנייה מוצג מנהל אובייקטים (object pooler) שכתבתי, שמטרתו להחליף את מנגנון הקישוריות (dependency) ל-COM+. מאחר של COM+ יש תקורה לא מעטה באריזת מידע
(marshaling) וגם כיוון שראיתי שיש גם כמה בריחות זיכרון במנגנון, החלטתי לכתוב מנגנון דומה ב-v1.1 אבל מהר מאוד נתקלתי בקשיים בירושת מבנים. מאחר ורציתי לחקות את המנגנון של COM+ כמה שניתן (כדי למנוע פגיעה באפליקציה קיימת כמה שניתן), כתבתי את המחלקות שלי שיוכלו לרשת ממחלקת בסיס שתתמוך במנגנון ה-pooling באופן דומה למה שמבוצע על ידי ה-ServicedComponent ב COM+.
הבעיה בגרסה v1,1 הייתה שהייתי צריך להעביר כל הזמן שכפולים של המבנים ובכל מבנה משוכפל, על מנת לתמוך במנגנון ה-pooling, הייתי צריך להגדיר שיגרה שנקראת “Create” על מנת לקבל את האובייקט מה-pool. בשכתוב בגרסה 2.0 הייתי מסוגל לבטל את רוב הצורך בהעברת System.Type ממקום למקום על ידי העברת פרמטרים כלליים והייתי מסוגל לבטל את הצורך ביצירת השגרה גל מנת לשלוף את האובייקט מה pool. הדבר היחידי שאני עדיין לא מרוצה ממנו לגמרי הוא הצורך להגדיר overload לבנאי שמקבל פרמטר בוליאני, מאחר ו-type לא יורשים בנאים.
בכל מקרה, לא אסביר את הקוד לעומק במאמר זה אבל, אם ברצונכם ללמוד יותר לעומק ולקבל דוגמאות יותר מעמיקות על generics באפשרותכם ללמוד מה ביצעתי באפליקציות אלו.
כדי להבין טוב יותר את החלק האחרון, אני ממליץ לכם לעבור על החלק של הבעיות שנפתרו, על מנת להיזכר ולדעת מתי ניתן להשתמש ב- generics. לאחר שתכירו טוב יותר את המינוחים והכתיבה חיזרו וקראו את החלק האחרון פעם נוספת וכן תציצו שוב בדוגמאות.
קישור לדוגמאות
מקורות נוספים
http://weblogs.asp.net - יש שם מספר בלוגים שמכסים נושא זה ונושאים אחרים ב ,NET 2.0
Generics Design Guidelines
MSDN Docs that ship with Whidbey
System.Collections.Generic Namespace
הקדמה ל generics – שנכתב על ידי רוב צ'רטייר
google - www.google.com הוא החבר הכי טוב שלנו, רק חפש “generics .net”
 
שלח עמוד לחבר הדפס עמוד לראש הדף