ReflectionContext

אחד הדברים שקרו ב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 עם המתודות הבאות:

1
2
3
4
5
6
public abstract class ReflectionContext
{
public abstract Assembly MapAssembly(Assembly assembly);
public abstract TypeInfo MapType(TypeInfo type);
public virtual TypeInfo GetTypeForObject(object value);
}

יש כאן שלוש מתודות: 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 שנקבל.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MyReflectionContext : CustomReflectionContext
{
protected override IEnumerable<object> GetCustomAttributes
(MemberInfo member,
IEnumerable<object> declaredAttributes)
{
if (IsKeyValuePairProperty(member))
{
return new[] {new MyAttribute(member.Name)};
}
return base.GetCustomAttributes(member, declaredAttributes);
}
private static bool IsKeyValuePairProperty(MemberInfo member)
{
return (member is PropertyInfo) &&
IsTypeKeyValuePair(member.DeclaringType);
}
private static bool IsTypeKeyValuePair(Type type)
{
return type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof (KeyValuePair<,>);
}
}
#region Attribute
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public sealed class MyAttribute : Attribute
{
private readonly string mName;
public MyAttribute(string name)
{
mName = name;
}
public string Name
{
get
{
return mName;
}
}
}
#endregion

שימוש:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyReflectionContext reflectionContext = new MyReflectionContext();
KeyValuePair<string, int> keyValuePair =
new KeyValuePair<string, int>("Ten", 10);
Type contextType =
reflectionContext.GetTypeForObject(keyValuePair);
PropertyInfo keyProperty = contextType.GetProperty("Key");
MyAttribute attribute =
keyProperty.GetCustomAttribute<MyAttribute>();
string name = attribute.Name; // Key

בקשר לבעיה השנייה: לצערי, מסתבר יותר מסובך לפתור בעיה זו משחשבתי. להלן השימוש שאליו התכוון המשורר:

לCustomReflectionContext יש מתודה בשם AddProperties שניתן לדרוס על מנת להוסיף Properties דינאמיים לType. אלה Properties קבועים לכל הInstanceים של הType ולא Properties לפי Instance.

למשל, נניח שיש לנו את הטיפוס הזה שלא שייך לנו

1
2
3
4
5
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

אז נוכל להוסיף לו Property דינאמי בשם FullName בצורה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyOtherReflectionContext : CustomReflectionContext
{
protected override IEnumerable<PropertyInfo> AddProperties(Type type)
{
if (type == typeof (Person))
{
PropertyInfo fullName =
this.CreateProperty
(this.MapType(typeof (string).GetTypeInfo()),
"FullName",
x =>
{
// Getter
Person person = x as Person;
return person.FirstName + " " + person.LastName;
},
(x, value) =>
{
// TODO: write setter
}
);
yield return fullName;
}
}
}

ואז הקוד הבא יעבוד לנו:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyOtherReflectionContext reflectionContext = new MyOtherReflectionContext();
Person person = new Person() {FirstName = "Davis", LastName = "Motomiya"};
TypeInfo type = reflectionContext.GetTypeForObject(person);
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
Console.WriteLine("{0} - {1}", propertyInfo.Name,
propertyInfo.GetValue(person));
}
//FirstName - Davis
//LastName - Motomiya
//FullName - Davis Motomiya

המשך יום עם קונטקסט משתקף לטובה,
אלעד

שתף