אחד הדברים החזקים שאפשר לעשות עם MethodInfo הוא לקרוא לפונקציות גנריות, כשאנחנו לא יודעים מהו הטיפוס הגנרי.
למשל, נניח שכתבנו ממשק גנרי כזה:
|
|
עכשיו נתון לנו אוסף של אובייקטים. מתוכו אנחנו מעוניינים לחלץ את כל האובייקטים מטיפוס כזה, כאשר לכל אחד כזה יכול להיות פרמטר גנרי אחר, ומתוכו ליצור אוסף חדש של טיפוסים חדשים ע"י קריאה למתודה Clone.
היינו מנסים לכתוב קוד כזה:
|
|
נראה מבטיח, אלא שזה לא יתקמפל…
בעיה זו היא די נדירה כאשר הטיפוסים והממשקים מעוצבים נכון. למרבה הצער, לא תמיד הכל מעוצב נכון.
נוכל לפתור בעיה זו באמצעות שימוש בReflection וקריאה לMethodInfo המתאים:
ניצור פונקציה כזאת:
|
|
ונקרא לה בReflection:
|
|
טוב, יש פה די הרבה קוד, אבל מה שקורה כאן זה שאנחנו בודקים עבור כל טיפוס אם הוא מממש ICloneable<> כלשהו, ובמידה וכן, אנחנו קוראים לפונקציה Clone (זו שכתובה למעלה) עם הפרמטר הגנרי של אותו ICloneable<>. כפי שכתבתי קודם, אם הטיפוסים מעוצבים מספיק טוב, לא כל כך נתקלים בבעיה זו.
למשל, אם היינו דואגים שיהיה גם ממשק לא גנרי, למשל:
|
|
אם היינו דואגים שכל מי שיממש את הטיפוס ICloneable<T> יממש גם ICloneable, היינו יכולים פשוט לעשות הסבה לICloneable ולהשתמש בו.
אבל למה הכוונה לדאוג? אפשר לעשות שICloneable<T> יממש ICloneable, אבל אז צריך לממש את אותה מתודה פעמיים. (ומי מבטיח לנו שמממשים אותה נכון?)
אפשר לסמוך על המממשים שיממשו גם ICloneable וגם ICloneable<T>, אבל מה אם הם לא יממשו?
בכל מקרה, ממשק לא גנרי יכול לעזור לפתור את הבעיה הזו, אבל גם לו יש מספר חסרונות, למרות שמומלץ להשתמש בו.
אנחנו נראה בעתיד עוד דרכים לפתור את הבעיה הזו. (ראו גם טיפ מספר 231)
סופ"ש גנרי משתקף לטובה