בעבר הרחוק (טיפ מספר 150) דיברנו על כך שלפעמים נרצה לקרוא לפונקציה גנרית עם פרמטר גנרי לא ידוע.
זה מצב שנדיר להיתקל בו כאשר הטיפוסים שלנו מעוצבים היטב. למרבה הצער, לא תמיד הטיפוסים שלנו מעוצבים היטב.
תזכורת: יש לנו איזשהו ממשק גנרי:
|
|
כעת יש לנו אוסף של אובייקטים, חלקם מהסוג הזה (כל אחד עם פרמטר גנרי אחר), ואנחנו מעוניינים ליצור מהם אוסף חדש של אובייקטים ע"י הפעלת הפונקציה Clone על כל אחד מהם.
הדרך שלא עובדת:
|
|
שתי השורות הראשונות בתוך הלולאה לא מתקמפלות.
ראינו דאז כיצד ניתן לפתור בעיה זו באמצעות Invoke של MethodInfo, אלא שזה דרש מאיתנו לכתוב די הרבה קוד.
כעת נראה איך אפשר לפתור בעיה זו בעזרת Dynamic Binding של C# 4.0.
הפתרון הראשון הוא כזה:
|
|
פתרון זה בעייתי ממספר סיבות:
- הוא ממש מכוער – אנחנו מוודאים האם הפונקציה קיימת או לא ע"י תפיסת Exception.
- הוא שגוי לוגית – בשום מקום אנחנו לא מוודאים שהטיפוס מממש את ICloneable<T>, כך שאם יש לאובייקט שאנחנו מריצים עליו את הפונקציה פונקציה Clone אחרת, עדיין היא תכנס לתוצאה.
- הפתרון הזה יכול לא לעבוד אפילו אם הטיפוס שלנו מממש ICloneable<T>. מה זאת אומרת? כשדיברנו על Dynamic Binding לראשונה, ציינתי שDynamic Binding יודע למצוא רק מתודות שהן public. ובכן, אם הטיפוס שלנו מממש את הממשק, אבל באופן Explicitly, יזרק לנו Exception, ולכן הוא לא עושה את מה שרצינו. (ראו גם טיפ מספר 82)
אז מה אפשר לעשות?
אם נזכר בטיפ הקודם, קריאה דינאמית לפונקציה עם פרמטר גנרי, מבצעת הזרקה בזמן ריצה לפרמטר הגנרי המתאים ביותר.
נוכל לנצל עובדה זו כדי לפתור את הבעיה הנ"ל:
נכתוב פונקציה גנרית כזאת:
|
|
ואז נכתוב את הקוד הבא:
|
|
כעת התגברנו על בעיות 2 ו3 – לא נכנס לפונקציה Clone במידה ואנחנו לא מממשים את הממשק. במידה וכן, הInstance שלנו הוא כבר מסוג ICloneable<T> ולכן נוכל לקרוא לפונקציה של הממשק.
עם זאת לא התגברנו על הבעיה המעצבנת של תפיסת Exception.
נוכל לפתור בעיה זו בצורה אלגנטית ע"י הוספת פונקציה כזו:
|
|
כעת נוכל לעשות משהו כזה:
|
|
מה קורה כאן? כאשר אנחנו קוראים לClone, בזמן ריצה נבחר הOverload המתאים ביותר.
אם אנחנו מממשים את הממשק ICloneable<T>, יבחר הoverload הראשון. אחרת יבחר הoverload השני.
לכן אם אנחנו לא מממשים את הממשק ICloneable<T>, נקבל פשוט null.
זה לא פותר את הבעיה במידה ואנחנו מממשים את הממשק, והפונקציה Clone מחזירה null, אבל די פשוט להתגבר על המקרה הזה.
המשך יום דינאמי טוב!