الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

[مقالة] البرمجة الديناميكية في سي شارب 4

بدأه عبد العظيم بخاري في 18 أكتوبر 2010 · 0 رد · 757 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1

محتويات المقالة :

- التعرف على بيئة تشغيل اللغة الديناميكية Dynamic Language Runtime.

- نوع dynamic.

- DLR Script Runtime.

- انشاء انواع داينمك مخصصة باستعمال DynamicObject.

- انشاء انواع داينمك مخصصة باستعمال ExpandoObject.

ازدهرت في الفترة الأخيرة عدة لغات ديناميكية مثل الجافاسكريبت والPython و Ruby وقد ازداد استعمالها بشكل كبير . قامت C# بدورها في الإصدار السابق باضافة الكلمة المحجوزة var والanonymous methods لتشق طريقها باتجاه البرمجة الديناميكية .

ومع الإصدار الرابع ل.NET تم اضافة الكلمة المحجوزة dynamic لتعطينا الكثير من الإمكانيات الديناميكية التي كان يحلم بها كثير من المبرمجين مع أن لغة C# بحد ذاتها هي لغة ذات نوع ستاتيكي كما نعلم.

سنتعرف في هذه المقالة على نوع dynamic وسنرى قواعد استعماله وسناهد ايضاً كيفية عمل implementation للDynamic Object اضافة لكيفية استعماله.

بيئة تشغيل اللغة الديناميكية Dynamic Language Runtime

يجب أن تعلم أن الإمكانيات والمزايا الديناميكية في لغة C# 4 هي جزء من Dynamic Language Runtime أو ما نختصرها ب DLR وهي عبارة عن مجموعة من الخدمات التي تم اضافتها إلى الCLR من اجل السماح باضافة اللغات الديناميكية كRuby و Python والسماح للغة C# بأن تمتلك بعض المزايا الديناميكية التي تمتلكها تلك اللغات .

ملاحظة : يوجد هناك نسخة مفتوحةالمصدر من DLR وهي موضوعة على موقع CodePlex وتمت اضافة نفس النسخة ضمن اطار عمل .NET الجديد .

تم اضافة الDLR في اطار عمل .NET ضمن الnamespace التي تسمى System.Dynamics اضافة لبعض الكلاسات التي تم اضافتها في الnamespace التي تدعى System.Runtime.CompilerServices .

يستعمل الDLR ايضاً السلفرلايت وتستعمله كذلك لغات اخرى مثل IronRuby و IronPython وهما النسخ مفتوحة المصدر من Ruby و Python لذلك اصبح بالإمكان اضافة مزايا هذه اللغات والسكربتات إلى تطبيقاتك عن طريق عمل host لل DLR حيث يسمح لك الruntime بتمرير المتغيرات من وإلى السكريبت .

نوع dynamic

يسمح لك نوع dynamic بكتابة كودات يتم فحصها في وقت التشغيل runtime وليس اقناء وقت الترجمة compile time كما جرت العادة وهذا يعني أن نوع dynamic سوف يقوم بعملية تجاوز للكومبايلر حيث أن الاخير سيفترض أن جميع العمليات لاي object من نوع dynamic هي عمليات صحيحة وفي حال كان أي منها خاطئ فلن يتم اكتشاف الخطأ قبل وقت التشغيل runtime .

انظر للكود التالي :

class Program
{
static void Main(string[] args)
{
var staticPerson = new Person();
dynamic dynamicPerson = new Person();
staticPerson.GetFullName("Abed", "Bukhari");
dynamicPerson.GetFullName("Abed", "Bukhari");
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GetFullName()
{
return string.Concat(FirstName, " ", LastName);
}
}

لن يتم ترجمة الكود السابق وذلك بسبب استدعاء staticPerson.GetFullName() حيث أنه لا توجد أي ميثود في الPerson تأخذ اثنين من الباراميترز لذلك سوف يعطينا الكومبايلر خطأ على هذا الأمر . لكن لو قمت بعمل comment لذلك السطر فسوق يتم ترجمة الكود وعند تشغيله سوف تحصل على runtime error مع العلم أن نوع الإستناء exception الذي يحصل هو RuntimeBinderException والRuntimeBinder هو عبارة عن object في الruntime يقوم بفحص الإستدعاء حتى يرى اذا ما كان يوجد في Person هذه الميثود التي تم استدعاءها فعلاً ام لا . سنناقش هذا الموضوع لاحقاً في هذه المقالة .

يختلف نوع dynamic عن نوع var بان الاول يمكنه تغيير نوعه خلال وقت التشغيل عدة مرات بعكس var الذي لا يمكنه تغيير نوع الobject الذي يعرف به وذلك لأن الكومبايلر يقوم بخمين هذا النوع عند اول قيمة تعطى لذلك الobject وبعد ذلك يثبت ذلك النوع ولا يمكن له أن يتغير .

يختلف تغيير النوع عن عمل casting لobject ما من نوع إلى اخر حيث انك عندما تعمل cast لobject ما من نوع إلى اخر ففي الواقع يتم انشاء object جديد من النوع الثاني . فمثلاً لا يمكنك عمل cast لobject من نوع int إلى نوع Person . اما في حال كان النوع dynamic فيمكنك عمل ذلك بكل سهولة .

//انظر لملف Dynamic\Program.cs

dynamic dyn;
dyn = 100;

Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);

dyn = "This is a string";
Console.WriteLine(dyn.GetType());
Console.WriteLine(dyn);

dyn = new Person() { FirstName = "Abed", LastName = "Bukhari" };

Console.WriteLine(dyn.GetType());
Console.WriteLine("{0} {1}", dyn.FirstName, dyn.LastName);

عندما يتم تنفيذ هذا الكود فسوف يظهر لك أن كائن dyn يتم تحويله في الواقع من System.Int32 إلى System.String إلى Person .

في حال قمت بتعريف dyn كint مثلاً أو string فلن يتم ترجمة الكود .

يوجد هناك بعض القيود على نوع dynamic فالobject من هذا النوع لا يدعم الextension methods اضافة للفنكشنات المجهولة anonymous functions و(Lambda Expressions) والتي لا يمكن استعمالها كباراميترز لاستدعاء ميثود ديناميكي لذلك لا تعمل LINQ جيداً مع dynamic objects حيث أن اغلب استدعاءات LINQ هي extension methods , والLamda expressions هم باراميترز لهذه الextension methods.

ماذا يحدث لنوع dynamic في الخلفية ؟

لغة C# كما نعلم هي لغة ستاتيكية لكن ما الذي يحدث خلف الكواليس حتى يتم اعطاءها المميزات الديناميكية ؟

لمعرفة ذلك انظر لكود IL الذي يتم توليده عندما يتم استعمال نوع dynamic .

انظر اولاً لكود سي شارب التالي :

using System;
namespace DeCompile
{
class Program
{
static void Main(string[] args)
{
StaticClass staticObject = new StaticClass();
DynamicClass dynamicObject = new DynamicClass();
Console.WriteLine(staticObject.IntValue);
Console.WriteLine(dynamicObject.DynValue);
Console.ReadLine();
}
}
class StaticClass
{
public int IntValue = 100;
}
class DynamicClass
{
public dynamic DynValue = 100;
}
}

لاحظ أن لدينا اثنين من الكلاسات وهما StaticClass و DynamicClass ويحتوي الأول على حقل واحد ذو قيمة int بينما يحتوي DynamicClass على حقل يرجع object من نوع dynamic . تقوم ميثود Main بعمل بسيط للغاية حيث تنشأ هذه الobjects وتطبع القيم التي ترجعها الmethods . قم الان بعمل comment لكل ما يتعلق بكلاس Dynamic في الMain كالاتي :

static void Main(string[] args)
{
StaticClass staticObject = new StaticClass();
//DynamicClass dynamicObject = new DynamicClass();
Console.WriteLine(staticObject.IntValue);
//Console.WriteLine(dynamicObject.DynValue);
Console.ReadLine();
}

يمكنك رؤية كودات IL التي يتم توليدها لميثود Main باستعمال اداة ildasm .

.method private hidebysig static void Main(string[] args) cil managed
{
 .entrypoint
 // Code size    26 (0x1a)
 .maxstack 1
 .locals init ([0] class DeCompile.StaticClass staticObject)
 IL_0000: nop
 IL_0001: newobj   instance void DeCompile.StaticClass::.ctor()
 IL_0006: stloc.0
 IL_0007: ldloc.0
 IL_0008: ldfld   int32 DeCompile.StaticClass::IntValue
 IL_000d: call    void [mscorlib]System.Console::WriteLine(int32)
 IL_0012: nop
 IL_0013: call    string [mscorlib]System.Console::ReadLine()
 IL_0018: pop
 IL_0019: ret
} // end of method Program::Main

لن نذهب بعيداً في تفاصيل IL لكن بالنظر لهذا الجزء الكود بامكانك أن تفهم ما يحدث فيه بسهولة ففي السطر 0001 مثلاً يتم استدعاء الconstructor للStaticClass اما في السطر 0008 فيتم اسدعاء الحقل IntValue لكلاس StaticClass ويتم طباعة القيمة في السطر الذي يعده .

قم الان بازالة الcomments عن الreferences ل DynamicClass وقم بوضع comment على الreferences التي لStaticClass كالاتي :

static void Main(string[] args)
{
//StaticClass staticObject = new StaticClass();
DynamicClass dynamicObject = new DynamicClass();
Console.WriteLine(staticObject.IntValue);
//Console.WriteLine(dynamicObject.DynValue);
Console.ReadLine();
}

قم بعمل كومبايل للتطبيق ولاحظ الكود الذي يتم توليده :

.method private hidebysig static void Main(string[] args) cil managed
{
 .entrypoint
 // Code size    121 (0x79)
 .maxstack 9
 .locals init ([0] class DeCompile.DynamicClass dynamicObject,
      [1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
 IL_0000: nop
 IL_0001: newobj   instance void DeCompile.DynamicClass::.ctor()
 IL_0006: stloc.0
 IL_0007: ldsfld   class [System.Core]System.Runtime.CompilerServices.CallSite`1<class[mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class[mscorlib]System.Type,object>> DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
 IL_000c: brtrue.s  IL_004d
 IL_000e: ldc.i4.0
 IL_000f: ldstr   "WriteLine"
 IL_0014: ldtoken  DeCompile.Program
 IL_0019: call    class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
 IL_001e: ldnull
 IL_001f: ldc.i4.2
 IL_0020: newarr   [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
 IL_0025: stloc.1
 IL_0026: ldloc.1
 IL_0027: ldc.i4.0
 IL_0028: ldc.i4.s  33
 IL_002a: ldnull
 IL_002b: newobj   instance void[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                        string)
 IL_0030: stelem.ref
 IL_0031: ldloc.1
 IL_0032: ldc.i4.1
 IL_0033: ldc.i4.0
 IL_0034: ldnull
 IL_0035: newobj   instance void[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                        string)
 IL_003a: stelem.ref
 IL_003b: ldloc.1
 IL_003c: newobj   instance void[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpCallFlags,
                                                           string,
                                                           class [mscorlib]System.Type,
                                                           class[mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                           class[mscorlib]System.Collections.Generic.IEnumerable`1<class[Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
 IL_0041: call    class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class[System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class[System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Create(class[System.Core]System.Runtime.CompilerServices.CallSiteBinder)
 IL_0046: stsfld   class [System.Core]System.Runtime.CompilerServices.CallSite`1<class[mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class[mscorlib]System.Type,object>> DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
 IL_004b: br.s    IL_004d
 IL_004d: ldsfld   class [System.Core]System.Runtime.CompilerServices.CallSite`1<class[mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class[mscorlib]System.Type,object>> DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
 IL_0052: ldfld   !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class[mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class[mscorlib]System.Type,object>>::Target
 IL_0057: ldsfld   class [System.Core]System.Runtime.CompilerServices.CallSite`1<class[mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class[mscorlib]System.Type,object>> DeCompile.Program/'<Main>o__SiteContainer0'::'<>p__Site1'
 IL_005c: ldtoken  [mscorlib]System.Console
 IL_0061: call    class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
 IL_0066: ldloc.0
 IL_0067: ldfld   object DeCompile.DynamicClass::DynValue
 IL_006c: callvirt  instance void class [mscorlib]System.Action`3<class[System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>::Invoke(!0,
                                                                                       !1,
                                                                                       !2)
 IL_0071: nop
 IL_0072: call    string [mscorlib]System.Console::ReadLine()
 IL_0077: pop
 IL_0078: ret
} // end of method Program::Main

يمكننا القول أن كومبايلر C# يعمل جهد اضافي من اجل دعم نوع dynamic , فبالنظر للكود الناتج يمكنك أن ترى references ل System.Runtime.CompilerServices.CallSite و System.Runtime.CompilerServices.CallSiteBinder .

يقوم نوع CallSite بمعالجة البحث اثناء وقت التشغيل runtime فعندما يتم عمل استدعاء لكائن من نوع dynamic في runtime فإن شيئ ما يجب أن يذهب ويلقي نظرة على الobjects لكي يرى هل ذلك الmember موجود حقاً ام لا .

يوم CallSite بتخزين هذه المعلومات ويعمل لها cash , لذلك لن تنفذ عملية البحث كل مرة وبدون هذه الخطوة فإن الاداء سيصبح سيئ للغاية .

بعد ان يقوم CallSite بالبحث عن الmember , يقوم CallSiteBinder باستدعاءه فهو يأخذ المعلومات من CallSite ويولَد expression tree تمثل العملية المرتبطة مع الbinder .

هناك كثير من الأمور التي تجري هنا لكن لا تخف لأن هناك عناية كبيرة من اجل تحسين هذه الأشياء التي تبدو معقدة للغاية لذلك يجب أن يكون استخدامك لنوع dynamic مفيد حقاً لبرامجك وليس لاي استخدام عشوائي .

DLR ScriptRuntime Hosting

هناك عدة جوانب من المزايا التي تحصل عليها عند عمل host لDLR ScriptRuntime ضمن برامجك , تخيل أنه بامكانك اضافة مزايا السكريبتات إلى برنامج ما وتخيل ايضاً أنك تستطيع تمرير القيم من وإلى السكريبت بكل سهولة وليس هذا فقط بل يمكنك ايضاً استدعاء methods لكائنات الdynmics تكون موجودة داخل السكريبت ومن هنا يمكن للتطبيق أن يأخذ مزايا العمل التي يقوم بها ذلك السكريبت .

هناك الان عدة لغات برمجية يمكن جعلهم كhost للغات السكريبتات وهم IronPython و IronRuby والجافاسكريبت .

يمكنك ان تمتلك ايضا ميزةمع ScriptRuntime تتيح لك تنفيذ اجزاء من كودات أو حتى سكريبت كامل مخزن في ملف ما . يمكنك اختيار اللغة المناسبة لمحرك التنفيذ أو أن تدع الDLR يحدد أي محرك مناسب التشغيل السكريبت .

يمكن للسكريبت أن ينشأ داخل application domain خاص به أو الapplication domain الحالي .

مع كل هذه الكمية من المرونة فإن هناك استخدامات لا حصر لها عند عمل host للScriptRuntime . يوضح المثال التالي احدى الطرق لاستخدام الScriptRuntime . نفترض أن هناك تطبيق تجاري للشراء احدى مميزاته انه يقوم بحساب كمية الخصم اعتماداً على اسس معينة . تتغير هذه الخصومات في كثير من الأحيان مثل اوقات الحملات التجارية التي تبدأ وتنتهي . هناك عدة طرق لعمل هذا الامر والمثال التالي يوضح هذا الشيئ عن طريق ScriptRuntime وبعض كودات الPython .

حتى نسهل الموضوع فهذا المثال هو تطبيق ويندوز بسيط مع العلم أن هذه الفكرة يمكن أن تكون ضمن مشروع ويب ضخم أو اي تطبيق اخر على سبيل المثال .

توضح الصورة التالية شكل مبسط لهذا التطبيق :

37770412.png

ياخذ التطبيق عدد العناصر والتكلفة الإجمالية لها ويطبق خصم اعتماداً على خيار الradio الذي قمت باختياره , وطبعا يكون هذا النظام اعقد بكثير في الحياة العملية لكن هذا المثال سنكتفي بهذه الخيارات .

هذا هو الكود الذي ينفذ الخصم :

//انظر لملف Window1.xaml.cs

private void button1_Click(object sender, RoutedEventArgs e)
{
string scriptToUse;
if (CostRadioButton.IsChecked.Value)
{
scriptToUse = "AmountDisc.py";
}
else
{
scriptToUse = "CountDisc.py";
}
ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
ScriptEngine pythEng = scriptRuntime.GetEngine("Python");
ScriptSource source = pythEng.CreateScriptSourceFromFile(scriptToUse);
ScriptScope scope = pythEng.CreateScope();
scope.SetVariable("prodCount", Convert.ToInt32(totalItems.Text));
scope.SetVariable("amt", Convert.ToDecimal(totalAmt.Text));
source.Execute(scope);
label5.Content = scope.GetVariable("retAmt").ToString();
}

يقوم الجزء الاول بتحديد أي سكريبت سيتم تطبيقه : AmountDisc.py أو CountDisc.py.

يقوم AmountDisc.py باجراء خصم اعتماداً على كمية الشراء (السعر الإجمالي) .

discAmt = .20
retAmt = amt
if amt > 100.00:
retAmt = amt-(amt*discAmt)

اقل كمية من اجل تطبيق الخصم هي 100.00$ وفي حال كانت الكمية اقل من ذلك فلن يكون هناك اي خصم , اما غير ذلك فسيتم خصم 20% من الكمية الكلية .

يطبق CountDisc.py الخصم اعتماداً على عدد العناصر التي تم شراءها .

discCount = 10
discAmt = .1
retAmt = amt
if prodCount > discCount:
 retAmt = amt-(amt*discAmt)

يجب أن يكون عدد العناصر التي تم شراءها اكثر من 10 من اجل الحصول على خصم 10% من التكلفة الكلية .

الخطوة التالية هي اعداد بيئة الScriptRuntime . هناك اربع خطوات محددة يجب تنفيذها : انشاء ScriptRuntime object واعداد الScriptEngine الملائم وانشاء ScriptSource واخيراً انشاء ScriptScope .

انشاء ScriptRuntime object هو نقطة البداية وهو الاساس من اجل عمل الhosting فهو يمثل الأرضية التحتية لبيئة الإستضافة .

يتم انشاء ScriptRuntime عن طريق استعمال الميثود ذات نوع static وهي CreateFromConfiguration .

انظر لملف app.Config الذي يوضح كيف يجب أن تكون :

<configuration>
<configSections>
<section
name="microsoft.scripting"
type="Microsoft.Scripting.Hosting.Configuration.Section,
Microsoft.Scripting,
Version=0.9.6.10,
Culture=neutral,
PublicKeyToken=null"
requirePermission="false" />
</configSections>
<microsoft.scripting>
<languages>
<language
names="IronPython;Python;py"
extensions=".py"
displayName="IronPython 2.6 Alpha"
type="IronPython.Runtime.PythonContext,
IronPython,
Version=2.6.0.1,
Culture=neutral,
PublicKeyToken=null" />
</languages>
</microsoft.scripting>
</configuration>

يعرف الكود قسم خاص section من اجل "microsoft.scripting" ويضع بعض الproperties من اجل محرك لغة IronPython .

سوف تحصل بعد ذلك على رفرنس إلى ScriptEngine من ScriptRuntime . نحتاج في مثالنا إلى Python engine لكن ScriptRuntime سوف تمتلك القدرة على تحديد هذا الامر بسبب وجود امتداد py للسكريبت .

يقوم ScriptEngine بعملية تنفيذ كود السكريبت وهناك عدة methods لتنفيذ السكريبتات من الملفات او من اجزاء الكودات . يعطينا ScriptEngine ايضاً الScriptSource و الScriptScope .

كائن ScriptSource هو الذي يعطينا امكانية الوصول للسكريبت فهو يمثل السورس كود له حيث يقوم بتحميله من القرص وتحليله سطراً بعد الاخر ويقوم ايضاً بترجمة السكريبت داخل CompileCode object وهذا الأمر مفيد في حال تم تنفيذ السكريبت نفسه .

كائن ScriptScope هو في الاساس namespace . حيث اننا عندما نمرر قيمة داخل أو خارج السكريبت يلزمنا أن نربط المتغير بالScriptScope . قمنا في مثالنا باستدعاء ميثود SetVariable من اجل تمرير متغير prodCount ومتغير amt إلى سكريبت الPython , وهذه القيم هي في الواقع قيم الtextboxes الموجودين في تطبيق الويندوز totalItems و totalAmt .

يتم استقبال كمية الخصم المحسوبة في السكريبت عن طريق استعمال ميثود GetVariable وفي مثالنا هذه القيمة هي في متغير retAmt .

يمكنك ان ترى كيفية استدعاء ميثود من كائن الPython عن طريق النظر لزر CalcTax .

يحتوي سكريبت CalcTax.py على ميثود بسيطة جداص تأخذ القيمة المدخلةوتضيف عليها 15% من الضرائب وترجع القيمة الجديدة .

def CalcTax(amount):
  return amount*1.150

لاحظ كود سي شارب اللازم لاستدعاء تلك المثيود :

private void button2_Click(object sender, RoutedEventArgs e)
{
ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
dynamic calcRate = scriptRuntime.UseFile("CalcTax.py");
label6.Content = calcRate.CalcTax(Convert.ToDecimal(label5.Content)).ToString();
}

قم الان بانشاء ScriptRuntime object مرة اخرى عن طريق نفس الإعدادات التي تكلمنا عنها قبل قليل .

calcRate هو عبارة عن ScriptScope object قمنا بتعريفه كdynamic لذلك من السهل استدعاء ميثود CalcTax.

هذا مثال عن مقدار السهولة والبساطة التي جاء بها نوع dynamic .

كائنات DynamicObject و ExpandoObject

ماذا تفعل اذا اردت انشاء كائن ديناميكي خاص بك ؟

هناك عدة اختيارات لعمل ذلك , اما أن تشتق من DynamicObject أو عن طريق استعمال ExpandoObject .

باستعمال DynamicObject سيكون عليك العمل اكثر لأنك تحتاج لعمل override لعدة methods . اما ExpandoObject فهو كلاس جاهز من نوع sealed .

كائنات DynamicObject

لو كان عندنا object يمثل Person ما على سبيل المثال . فإننا سوف نحتاج لتعريف بعض الproperties كالاسم الأول واسم الأب واسم العائلة .. الخ . تخيل الان أن عندك ميزة تتيح لك انشاء هذا الobject اثناء الruntime دون وجود علم مسبق لدى النظام بالproperties والmethods التي يمتلكها هذا الobject .

هذه هي في الواقع ميزات الobject المشتق من DynamicObject فنلقي نظرة معاً على هذا الامر :

//انظر لملف Dynamic\Program.cs

class MyDynamicObject : DynamicObject
  {
    Dictionary<string, object> _dynamicData = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
      bool success = false;
      result = null;
      if (_dynamicData.ContainsKey(binder.Name))
      {
        result = _dynamicData[binder.Name];
        success = true;
      }
      else
        result = "Property Not Found!";

      return success;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
      _dynamicData[binder.Name] = value;
      return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
      dynamic method = _dynamicData[binder.Name];
      result = method((DateTime)args[0]);
      return result != null;    
    }

  }

قمنا في هذا المثال بعمل override لثلاثة methods وهم TrySetMember و TryGetMember و TryInvokeMember.

تعمل ميثود TrySetMember على اضافة ميثود أو property جديدة أو حقل جديد إلى الobject .

قمنا في هذا المثال ايضا بتخزين معلومات الmember في Dictionary object .

كائن SetMmemberBinder هو الذي يتم تمريره داخل ميثود TrySetMember ويحتوي على الproperty التي اسمها Name والتي تستعمل لتحديد العنصر في الDictionary .

تستقبل ميثود TryGetMember الobject المخزن في الDictionary اعتماداً على الproperty التي تسمى GetMemberBinder.Name

لكن كيف سنستعملهم ؟

يوضح الكود التالي طريقة لاستعمال الكائن الديناميكي الجديد الذي انشأناه للتو .

dyn = new MyDynamicObject();
dyn.FirstName = "Abed";
dyn.LastName = "Bukhari";
Console.WriteLine(dyn.GetType());
Console.WriteLine("{0} {1}", dyn.FirstName, dyn.LastName);

يبدو الكود بسيط للغاية لكن اين هو استدعاء الmethods الذين قمنا بعمل override لهم ؟

هنا يقوم اطار عمل ال.NET بمساعدتنا حيث يقوم DynamicObject بمعالجة الربط لنا وكل ما علينا فعله فقط هو ان نؤشر على الproperties الذين اسمائهم : FirstName و LastName كما لو كانوا موجودين طيلة الوقت دائماًُ .

لكن ماذا عن اضافة ميثود ؟

هذا يمكن أن يتم بسهولة حيث يمكنك استعمال MyDynamicObject واضافة ميثود GetTomorrowDate اليه والتي تأخذ كائن DateTime وترجع تاريخ اليوم التالي كstring .

لاحظ الكود التالي:

dynamic dyn = new MyDynamicObject();
Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString();
dyn.GetTomorrowDate = GetTomorrow;
Console.WriteLine("Tomorrow is {0}", dyn.GetTomorrowDate(DateTime.Now));

لقد قمنا بانشاء الdelegate المسمى GetTommorow باستعمال Func<T,TResult>

ويؤشر هذا الdelegate على اسدعاء AddDays حيث يتم اضافة يوم واحد على التاريخ الذي يمرر ويتم ارجاع التاريخ الناتج بstring .

يتم وضع الdelegate بعدها إلى GetTomorrowDate في myDynObject حيث يقوم السطر الأخير باستدعاء الميثود الجديدة ويمرر لها تاريخ اليوم .

لقد حصلنا بعد هذا كله على سحر ديناميكي حيث اصبح لدينا object يمتلك ميثود صالحة للاستعمال .

كائنات ExpandoObject

يعمل ExpandoObject بشكل مشابه لMyDynamicObject والذي قمنا بانشاءه في القسم الماضي , لكن الفرق هو أنه لن يتم عمل override لاي من الmethods كما هو مبين بالكود التالي .

static void DoExpando()
    {
      dynamic expObj = new ExpandoObject();
      expObj.FirstName = "Abed";
      expObj.LastName = "Bukhari";
      Console.WriteLine(expObj.FirstName + " " + expObj.LastName);
      Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToShortDateString();
      expObj.GetTomorrowDate = GetTomorrow;
      Console.WriteLine("Tomorrow is {0}", expObj.GetTomorrowDate(DateTime.Now));

      expObj.Friends = new List<Person>();
      expObj.Friends.Add(new Person() { FirstName = "Abed", LastName = "Halabi" });
      expObj.Friends.Add(new Person() { FirstName = "Amjad", LastName = "Mousa" });
      expObj.Friends.Add(new Person() { FirstName = "Hamza", LastName = "Mohmmad" });

      foreach (Person friend in expObj.Friends)
      {
        Console.WriteLine(friend.FirstName + " " + friend.LastName);
      }
    }

لاحظ ان هذا الكود مطابق تقريباً للذي رأيناه قبل قليل حيث أننا انشئنا الproperties التي اسمائها : FirstName و LastName اضافة لميثود GetTomorrow لكن هناك شيئ جديد وهو مجموعة كائنات Person اضفناها كproperty للobject.

قد لا يبدو هناك اي اختلاف مقارنة مع استخدام نوع dynamic لكن في الواقع هناك عدة فروق دقيقة و مهمة .

اولاً لا يمكنك انشاء object خالي من نوع dynamic فيجب أن تعطيه قيمة ما أو شيئ ما . فمثلاً لو كان عندك الكود التالي :

dynamic dynObj;
dynObj.FirstName = "Abed";

هذا الامر ممكن مع ExpandoObject كما لاحظنا من المثال السابق .

الأمثر الثاني وهو لان نوع dynamic يجب أن يتم اعطاءه قيمة او شيئ ما فسوف يقدم تقرير عن نوع الشيئ الذي اعطي له في حال قمت باستدعاء GetType . لذلك لو قمت باعطاءه قيمة int مثلاً فسوف يعطيك معلومات على أنه int وهذا الامر لا يحدث مع ExpandoObject أو أي object مشتق من DynamicObject .

اذا كنت تريد التحكم باضافة الproperties والوصول اليها لكائن dynamic فالإشتقاق من DynmicObject هو الخيار الأفضل حيث انك تستطيع معه استعمال عدة methods من اجل عمل override والتحكم بدقة حول كيفية تفاعل الobject مع الruntime .

اما الحالات الاخرى فقم باستعمال نوع dynamic او ExpandoObject .

خلاصة المقالة

تكلما في هذه المقالة عن نوع dynamic الجديد وشرحنا كيفية استعماله وقد رأينا ماذا يفعل الكومبايلر عندما يرى هذا النوع .

قمنا ايضاً بمناقشة البيئة التشغيلية DLR وقد قمنا بوضع تطبيق بسيط على ذلك .

قمنا باستعمال لغة Python كطريقة لاستخدام الDLR وشغلنا سكريبتاتها مع تمرير قيم منها وإليها . وفي النهاية قمنا بانشاء نوعنا الديناميكي الخاص عن طريق الإشتقاق من DynamicObject او استعمال ExpandoObject .

اصبح النطوير الديناميكي مشهور جداً في الفترة الأخيرة حيث يسمح لك بعمل اشياء صعبة جداً في اللغات الستاتيكية بكل بساطة . ويسمح نوع dynamic والDLR لمبرمجي C# بأن يمتلكوا الامتيازات والقدرات الديناميكية داخل برامجهم .

ملفات المقالة:

http://www.4shared.com/file/8EH62zQx/dynamicel-bukharicom.html

عبد العظيم بخاري

http://www.el-bukhari.com/2010/10/4_18.html

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…