אחד הדברים שקרו בFramework 4.5 הוא ששדרגו את העבודה עם Reflection.
זה בא לידי ביטוי בכל מיני Extension Methods חדשים, ביניהם הExtension Methods שיש לעבודה עם Custom Attributes במחלקה CustomAttributeExtensions והExtension Methods במחלקה RuntimeReflectionExtensions, מתודות חדשות שנוספו לכל מיני MemberInfoים (כגון CreateDeleage של MethodInfo) ובעוד מספר Featureים חדשים (למשל TypeInfo).
אחד הדברים שקרו הוא שנוספה אפשרות לCustomized Reflection.
למה הכוונה?
לא אחת יוצא לנו לכתוב איזשהו מנגנון המרה משלנו שממיר אובייקטים מסוג אחד לאובייקטים מסוג אחר באמצעות Reflection. למשל, סירלוז אובייקטים לפורמט אחר (בינארי/Xml/Json), או העתקת Properties מאובייקט אחד לאובייקט אחר, וכו’.
בד”כ המנגנונים האלה עוברים על הProperties של הType שאנחנו מעוניינים לעבד בReflection ועושים איתם משהו.
מה קורה אם אנחנו מעוניינים שמנגנון כזה יתייחס (או לחלופין לא יתייחס) לProperty מסוים? בד”כ בכדי לציין זאת, אנחנו שמים Attribute מעל הProperty שמציין, למשל, שאת הProperty הזה אנחנו רוצים לסרלז. (לפעמים אנחנו מציינים עוד הגדרות בAttribute, כמו באיזה שם לסרלז את הProperty, אבל לא נתייחס לזה כרגע)
כלומר, בד”כ המנגנונים האלה שאנחנו כותבים שרצים על המחלקות בReflection, ניזונים מAttributeים שהמשתמש שם על הTypeים והMemberים שלו.
מה הבעיה?
הבעיה היא שלפעמים אנחנו מעוניינים גם לתמוך בטיפוסים שכבר קיימים, ואין לנו אפשרות לערוך אותם. הדוגמה הקלאסית, היא הדוגמה של Dictionary - הרבה פעמים נכתוב איזשהו מנגנון שיודע לעבוד עם מחלקות של המשתמש, אבל הוא לא ידע להתמודד עם Dictionary, משום שלטיפוס KeyValuePair אין Attributeים שמציינים שצריך, למשל, לסרלז את הKey ואת הValue שלו.
בעיה נוספת היא שלפעמים אנחנו מעוניינים שהמנגונים יזהו Properties דינאמיים, למשל, נניח שטיפוס מסוים מחזיק איזשהו Dictionary, ואנחנו מעוניינים שהמנגנון יתייחס לאיברים בDictionary כאילו הם Properties של האובייקט.
כבר בעבר ניסו לפתור בעיה זו, למשל ע”י שימוש בTypeDescriptor המאפשר לנו לערוך Typeים בצורה Customized. הבעיה היא שמדובר בשפה מקבילה לReflection, וקשה לעבוד עם שתיהן בו זמנית.
RelectionContext נוסף בFramework 4.5 לבקשת צוות MEF, והוא נותן מענה לחלק מהבעיות.
אז מה זה בכלל? ReflectionContext היא מחלקה אבסטרקטית היושבת בmscorlib תחת הnamespace ששמו System.Reflection עם המתודות הבאות:
|
|
יש כאן שלוש מתודות: MapAssembly הממפה Assembly אחד לAssembly אחר, MapType הממפה TypeInfo אחד לTypeInfo אחר וGetTypeForObject המקבלת אובייקט ומחזירה את הType המתאים לו. (אני לא ארחיב כאן על TypeInfo, אבל בגדול מדובר בApi יותר נוח לType שנוסף בFramework 4.5)
הרעיון הוא שהמנגנונים שנכתוב יעבדו עם ReflectionContext, ואז במקום לקרוא לGetType ישירות על האובייקטים שמועברים למנגנון שלנו, כמו שנהגנו עד כה, נקרא לGetTypeForObject וכך נקבל תצוגה של הType של האובייקט כפי המנגנון מעוניין לראות אותו.
באופן דומה, קיימות המתודות שממפות TypeInfo לTypeInfo וAssembly לAssembly, שהמנגנון שלנו ישתמש בהן במקומות שבהם הוא מקבל Type או Assembly, כדי לראות את אלה בתצוגה שרלוונטית אליו.
שימו לב שאפשרות מעניינת היא שהמנגנון שנכתוב יאפשר להזריק אליו מבחוץ ReflectionContext, וכך למשתמש תהיה שליטה על הMetadata שהמנגנון שלנו רואה.
איך משתמשים בזה?
לצורך השימוש הנפוץ, נכתבה מחלקה בשם CustomReflectionContext היושבת בdll חדש בשם System.Reflection.Context.dll.
נוכל לרשת ממנה בכדי לפתור את שתי הבעיות שציינתי בהתחלה: (יש לה מספר פונקציות שנוח לדרוס)
הפתרון של הבעיה הראשונה:
יהיה לנו נוח לדרוס את המתודה GetCustomAttributes בכדי להשפיע על איזה Attributeים יש לType שנקבל.
|
|
שימוש:
|
|
בקשר לבעיה השנייה: לצערי, מסתבר יותר מסובך לפתור בעיה זו משחשבתי. להלן השימוש שאליו התכוון המשורר:
לCustomReflectionContext יש מתודה בשם AddProperties שניתן לדרוס על מנת להוסיף Properties דינאמיים לType. אלה Properties קבועים לכל הInstanceים של הType ולא Properties לפי Instance.
למשל, נניח שיש לנו את הטיפוס הזה שלא שייך לנו
אז נוכל להוסיף לו Property דינאמי בשם FullName בצורה הבאה:
ואז הקוד הבא יעבוד לנו:
המשך יום עם קונטקסט משתקף לטובה,
אלעד