C# 4.0 Part 6 - DynamicObject class and IDynamicMetaObjectProvider interface
אחד הדברים המתסכלים בללמוד חומר על טכנולוגיה חדשה שעדיין לא יצאה בגרסה סופית, זה שבכל גרסת ניסיון נוספת ה - API עלול להשתנות, וזה מתסכל כי אף אחד לא מעדכן אותך שה - API שהיה קיים בגרסה אחת השתנה והוא נראה שונה לגמרי בגרסה הנוכחית, ב - PDC הודגם ממשק שנקרא IDynamicObject - הממשק הזה נעלם, לא קיים יותר. ולמעשה היות שכל הפוסטים שלי בנושא אינם מדברים על הגרסה הסופית של המוצר, יכול מאוד להיות שכש - C# 4.0 תצא לאויר בגרסה רשמית הדברים שוב ישתנו. אז קחו את זה לתשומת לבכם.
כמו שהבטחתי
בפוסט הקודם אני אראה בפוסט הנוכחי איך אפשר לשנות את ההתנהגות של dynamic object.
הדבר היחיד שאתם צריכים לעשות זה לרשת מ - class שנקרא DynamicObject
לדוגמא, אם אנחנו רוצים לכתוב אובייקט שמתנהג כמו Dictionary, רק שהגישה ל - keys תהיה גישה למאפיינים ולא דרך indexer, אפשר לכתוב את הקוד הבא:
public class DynamicDictionary : DynamicObject
{
private Dictionary<string, object> dic = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (!dic.ContainsKey(binder.Name))
{
dic.Add(binder.Name, value);
}
else
{
dic[binder.Name] = value;
}
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!dic.ContainsKey(binder.Name))
{
return base.TryGetMember(binder, out result);
}
result = dic[binder.Name];
return true;
}
}
והשימוש בו יראה כך:
dynamic d = new DynamicDictionary();
d.Name = "shlomo";
d.Age = 24;
Console.WriteLine(d.Name);
Console.WriteLine(d.Age);
ולמעשה יצרנו אובייקט (קסום) שכאילו יוצר מאפיינים בלי שבאמת הם קיימים - כשהשמות של המאפיינים הם בפועל המפתחות ב - dictionary.
דוגמא נוספת, יותר שימושית היא - גישה לאלמנטים של xml באמצעות מאפיין ולא באמצעות indexer, פוסט ששווה לקרוא בנושא,
כאן. (הדוגמא הבאה דומה לדוגמא בפוסט)
שימו לב לקוד:
class DynamicXmlElement : DynamicObject
{
public XmlElement Element { get; private set; }
public DynamicXmlElement(XmlElement element)
{
Element = element;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
List<XmlElement> elements = new List<XmlElement>();
foreach (XmlElement item in Element)
{
if (item.Name == binder.Name)
{
elements.Add(item);
}
}
if (elements.Count == 0)
{
return base.TryGetMember(binder, out result);
}
if (elements.Count == 1)
{
result = new DynamicXmlElement(elements[0]);
return true;
}
result = elements.ConvertAll<DynamicXmlElement>(e => new DynamicXmlElement(e));
return true;
}
public override string ToString()
{
return Element.InnerText;
}
}
נעבור על הקוד:
ה - class יורש מ - DynamicObject ב - Ctor הוא מקבל אובייקט מסוג XmlElement שהוא עובד איתו.
המתודה העיקרית TryGetMember מקבלת שני פרמטרים: הראשון - זה ה - binder שמכיל מידע אודות הבקשה והשני הוא result שאמור להחזיר את התשובה.
אני רץ על כל האלמנטים ב - Element בודק האם יש בתוכו אלמנטים עם השם שנשלח,
במידה ולא מצאתי שום אלמנט אני פונה ל - base שבסופו של דבר יחזיר Expression של זריקת שגיאה.
במידה ומצאתי רק אחד - אני מחזיר מופע חדש של DynamicXmlElement כשאני שולח את האלמנט שמצאתי ל - Ctor.
במידה ומצאתי יותר מאחד, אני מחזיר List של DynamicXmlElement.
נניח שה - Xml נראה כך:
<Persons>
<Person>
<Name>shlomo</Name>
<Age>24</Age>
<Childern>
<Child>
<Name>yossi</Name>
<Age>6</Age>
</Child>
<Child>
<Name>meir</Name>
<Age>4</Age>
</Child>
</Childern>
</Person>
<Person>
<Name>noam</Name>
<Age>24</Age>
<Childern>
<Child>
<Name>david</Name>
<Age>3</Age>
</Child>
<Child>
<Name>mika</Name>
<Age>3</Age>
</Child>
</Childern>
</Person>
</Persons>
נניח שיש לי מאפיין שנקרא SampleXml שמחזיר אותו, הקוד ב - Main יראה כך:
XmlDocument doc = new XmlDocument();
doc.LoadXml(SampleXml);
dynamic dxe = new DynamicXmlElement(doc["Persons"]);
dynamic persons = dxe.Person;
foreach (dynamic item in persons)
{
Console.WriteLine(item.Name);
Console.WriteLine(item.Age);
foreach (dynamic child in item.Childern.Child)
{
Console.WriteLine(child.Name);
Console.WriteLine(child.Age);
}
}
שימו לב שהגישה לכל האלמנטים של ה - xml היא דרך מאפיינים ולא דרך indexer או מתודות. שזה נראה הרבה יותר נחמד.
במידה ואתם לא רוצים לרשת מ - DynamicObject כי זה חוסם לכם את אפשרות הירושה ממישהו אחר אפשר לממש את IDynamicMetaObjectProvider
public interface IDynamicMetaObjectProvider
{
DynamicMetaObject GetMetaObject(Expression parameter);
}
והקוד שלכם נראה כך:
class DynamicXmlDocument : XmlDocument, IDynamicMetaObjectProvider
{
#region IDynamicMetaObjectProvider Members
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new XmlDocumentMetaObject(parameter, this);
}
class XmlDocumentMetaObject : DynamicMetaObject
{
public XmlDocumentMetaObject(Expression parameter, object value)
: base(parameter, BindingRestrictions.Empty, value)
{
}
}
#endregion
}
אבל אז צריך לעשות override למתודות של DynamicMetaObject וזה באמת נושא לפוסט נפרד.