التجميعات (Assemblies)
تُصرَّف (compile) الأصناف التي تُعرِّفها، مَصحُوبة بتوابعها وخواصها وملفات البايتكود (bytecode) الخاصة بها، وتُحزَّم بداخل تجميعة (Assembly) تكون في صورة ملف يتضمن شيفرة مُصرَّفة جزئيًا بامتداد .dll
او .exe
. هذه التجميعات (Assemblies) هي المُكوِّن الأساسي لأي برنامج يتم تشغيله من خلال بيئة التنفيذ المشتركة (CLR).
تُعدّ التجميعات ذاتية التَوْثيق، فهي لا تَحتوِي على اﻷنواع وتوابعها وملفات اللغة الوسيطة (IL code) الخاصة بها فقط، بل أيضًا تَضُمّ البيانات الوَصفيّة (metadata) الضرورية للفَحْص والاستهلاك خلال زمني التَصرِّيف (compile time) والتشغيل (runtime).
تَملُك كل تجميعة (Assembly) اسم يَصِف هويتها المتفردة توصيفًا كاملًا:
// Will print: "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
تُعدّ أسماء التجميعات التي تحتوي على PublicKeyToken
أسماء صارمة (strong).
لِمَنح تجميعة اسمًا صارمًا (strong-naming)، لابّد أن يكون لديك زوجًا من المفاتيح، أحدهما عام (public key) والآخر سري (private key). يُوزَّع المفتاح العام مع التجميعة أما المفتاح السري فيُستخدَم لانشاء بصمة (signature) تُضاف لبيان التجميعة (Assembly manifest)، والذي يحتوي على أسماء جميع ملفات التجميعة وقيمها المقطّعة (hashes)، كما تُصبِح قيمة PublicKeyToken
جزءًا من اسمها.
التجميعات التي تَملُك نفس الاسم الصارم هي بالضرورة مُتطابقة، ومِنْ ثَمَّ يُمكِن الاعتماد على ذلك لتَجنُب تضارب أسماء التجميعات (assembly conflicts) وكذلك للإصدارة (versioning).
تنشئة تجميعة (assembly) ديناميكيًا
يُوفِّر إطار عمل .NET
عددًا من الأصناف والتوابع بفضاء الاسم System.Reflection.Emit
، والتي يُمكِن اِستخدَامِها لإنشاء تجميعة (assembly) بشكل ديناميكي. عامةً، تَضمّ أي تجميعة (assembly) وَحدة (module) واحدة أو أكثر، كلًا منها قد يتَضمَن صنف واحد أو أكثر.
مثلًا، يَتوفَّر التابع ModuleBuilder.DefineType
الذي يُمكِن اِستخدَامه لإضافة نوع جديد، ويُعيد قيمة من النوع TypeBuilder
. يُوفِّر هذا النوع بدوره العديد من التوابع لإضافة أعضاء (members) بالنوع المُنشَئ. فمثلًا، يُستخدَم التابع TypeBuilder.DefineField
لإضافة حَقْل، بينما يُستخدَم التابع TypeBuilder.DefineProperty
لإضافة خاصية. يَتوفَّر أيضًا التابعين TypeBuilder.DefineMethod
و TypeBuilder.DefineConstructor
لإضافة التوابع وبواني الكائن على الترتيب.
في المثال التالي، نَستعرِض طريقة تَنشئة تجميعة تَضُمّ وَحدة (module) وَاحدة تَشتمِل على تَعرِيف لنوع واحد يَحمِل الاسم MyDynamicType
. يتكون هذا النوع من:
-
حَقْل وحيد يُسمَى
m_number
من النوع العدديint
. -
خاصية مُناظِرة لهذا الحقل تَحمِل الاسم
Number
لها ضَابِط (setter) وجَالِب (getter). - بانيين للكائن (constructor) أحدهما بدون مُعامِلات والآخر يَستقبِل مُعامِل وحيد لتهيئة قيمة الحَقْل المبدئية.
-
التابع
MyMethod
والذي يَستقبِل مُعامِل من النوع العدديint
، ويُعيد حاصل ضرب قيمة المُعامِل في قيمة الحَقْلm_number
.
وبالتالي، يَكُون النوع المُراد إنشائه كالتالي:
public class MyDynamicType { private int m_number; public MyDynamicType() : this(42) {} public MyDynamicType(int initNumber) { m_number = initNumber; } public int Number { get { return m_number; } set { m_number = value; } } public int MyMethod(int multiplier) { return m_number * multiplier; } }
سنحتاج أولًا لاستدعاء التابعين DefineDynamicAssembly
و DefineDynamicModule
لتنشئة كائنين من النوع AssemblyBuilder
و ModuleBuilder
على الترتيب، يُمثل هذين التابعين كُلًا من التجميعة (assembly) والوَحدة (module) المُراد إنشائها، كالتالي:
AssemblyName aName = new AssemblyName("DynamicAssemblyExample"); AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly( aName, AssemblyBuilderAccess.RunAndSave ); // عادةً ما يكون اسم الوحدة هو نفسه اسم ملف التجميع عند تنشئة ملف تجميع من وحدة واحدة ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
للإعلان عن النوع MyDynamicType
داخل الوَحدة المُنشَأة، نَستخدِم الشيفرة التالية:
TypeBuilder tb = mb.DefineType("MyDynamicType", TypeAttributes.Public);
لإضافة الحَقْل m_number
بالنوع الجديد، نَستخدِم الشيفرة التالية:
FieldBuilder fbNumber = tb.DefineField( "m_number", typeof(int), FieldAttributes.Private);
لإضافة الخاصية Number
المُناظِرة للحَقْل، نَستخدِم الشيفرة التالية:
PropertyBuilder pbNumber = tb.DefineProperty( "Number", // اسم الخاصية PropertyAttributes.None, typeof(int), // نوع الخاصية new Type[0]);
لإضافة ضَابِط (setter) للخاصية المُنشَئة للتو، نَستخدِم الشيفرة التالية:
MethodBuilder mbSetNumber = tb.DefineMethod( "set_Number", // لعدم السماح باستدعاء الضابط لأنه تابع من نوع خاص MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), // لا يُعيد الضابط قيمة // يستقبل الضابط قيمة من النوع العددي new[] { typeof(int) }); // سنستخدم مولد الشيفرة الوسيطة IL generator للحصول على متن التابع il = mbSetNumber.GetILGenerator(); // لابد من تحميل this لأنه المُعامِل الأول لجميع التوابع il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); // حمل المعامل الثاني الذي يمثل القيمة المراد إسنادها للحقل il.Emit(OpCodes.Stfld, fbNumber); // خزن القيمة الجديدة المحملة للتو بالحقل il.Emit(OpCodes.Ret); // عُد // وأخيرًا، اربط التابع بضابط الخاصية pbNumber.SetSetMethod(mbSetNumber);
عادةً ما يَكُون اسم الضَابِط هو set_Property
.
بالمثل، لإضافة جَالِب (getter) لنفس الخاصية، نَستخدِم الشيفرة التالية:
MethodBuilder mbGetNumber = tb.DefineMethod( "get_Number", MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName, typeof(int), new Type[0]); il = mbGetNumber.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fbNumber); // حمل قيمة الحقل il.Emit(OpCodes.Ret); // أعد القيمة المحملة // وأخيرًا، اربط التابع بجالب الخاصية pbNumber.SetGetMethod(mbGetNumber);
لإضافة بواني الكائن بالنوع الجديد، نَضيِف الشيفرة التالية:
ConstructorBuilder intConstructor = tb.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { typeof(int) }); il = intConstructor.GetILGenerator(); // حمل this il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); // اِستدعي باني الأب // حمل this il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); // حمل قيمة المعامل الثاني الذي يمثل القيمة الممررة لباني الكائن il.Emit(OpCodes.Stfld, fbNumber); // خزن القيمة المحملة بالحقل il.Emit(OpCodes.Ret); var parameterlessConstructor = tb.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new Type[0]); il = parameterlessConstructor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldc_I4_S, (byte)42); // حمل القيمة 42 // استدعي this(42) il.Emit(OpCodes.Call, intConstructor); il.Emit(OpCodes.Ret);
لاحِظ أنه لابُدّ للبواني من استدعاء باني الصنف الأساسي أو بَانِي آخر بنفس الصنف.
لإضافة التابع MyMethod
، نَستخدِم الشيفرة التالية:
MethodBuilder mbMyMethod = tb.DefineMethod( "MyMethod", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); ILGenerator il = mbMyMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fbNumber); // حمل قيمة الحقل il.Emit(OpCodes.Ldarg_1); // حمل قيمة المعامل الممرر il.Emit(OpCodes.Mul); // احسب حاصل ضرب قيمة الحقل بقيمة المعامل il.Emit(OpCodes.Ret); // عُد
وأخيرًا نقوم بالتَنشئِة الفِعلّية للنوع الجديد عن طريق التابع CreateType
:
Type ourType = tb.CreateType();
الشيفرة بالكامل:
using System; using System.Reflection; using System.Reflection.Emit; class DemoAssemblyBuilder { public static void Main() { AssemblyName aName = new AssemblyName("DynamicAssemblyExample"); AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly( aName, AssemblyBuilderAccess.RunAndSave ); ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll"); TypeBuilder tb = mb.DefineType( "MyDynamicType", TypeAttributes.Public); FieldBuilder fbNumber = tb.DefineField( "m_number", typeof(int), FieldAttributes.Private); MethodBuilder mbMyMethod = tb.DefineMethod( "MyMethod", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); ILGenerator il = mbMyMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fbNumber); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Mul); il.Emit(OpCodes.Ret); PropertyBuilder pbNumber = tb.DefineProperty( "Number", PropertyAttributes.None, typeof(int), new Type[0]); MethodBuilder mbSetNumber = tb.DefineMethod( "set_Number", MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName, typeof(void), new[] { typeof(int) }); il = mbSetNumber.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, fbNumber); il.Emit(OpCodes.Ret); pbNumber.SetSetMethod(mbSetNumber); MethodBuilder mbGetNumber = tb.DefineMethod( "get_Number", MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName, typeof(int), new Type[0]); il = mbGetNumber.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fbNumber); il.Emit(OpCodes.Ret); pbNumber.SetGetMethod(mbGetNumber); ConstructorBuilder intConstructor = tb.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { typeof(int) }); il = intConstructor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, fbNumber); il.Emit(OpCodes.Ret); var parameterlessConstructor = tb.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new Type[0]); il = parameterlessConstructor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldc_I4_S, (byte)42); il.Emit(OpCodes.Call, intConstructor); il.Emit(OpCodes.Ret); Type ourType = tb.CreateType(); object ourInstance = Activator.CreateInstance(ourType); Console.WriteLine(ourType.GetProperty("Number") .GetValue(ourInstance)); ab.Save(@"DynamicAssemblyExample.dll"); var myDynamicType = tb.CreateType(); var myDynamicTypeInstance = Activator.CreateInstance(myDynamicType); Console.WriteLine(myDynamicTypeInstance.GetType()); var numberField = myDynamicType.GetField("m_number", BindingFlags.NonPublic | BindingFlags.Instance); numberField.SetValue (myDynamicTypeInstance, 10); Console.WriteLine(numberField.GetValue(myDynamicTypeInstance)); } }
الانعكاس (Reflection)
يُوفِّر الانعكاس العديد من الأصناف -منها الصنف Assembly
- والذي يَعمَل كمُغلِّف للتجميعة (assemblies). يُمكِن لبعض هذه الأصناف تَوفِير معلومات عن التجميعات المُحمَّلة من خلال البيانات الوَصفيّة (metadata) ببيان تلك التجميعات (Assembly manifest). يُمكِن استخدام بعض الأصناف الأُخرى لتَحمِيل ديناميكي للتجميعات، بل ولإنشاء أنواع جديدة واستدعائها ديناميكيا أثناء وقت التشغيل.
جَلْب بيانات عن تجميعة باستخدام الانعكاس
اِستخدِم الشيفرة التالية لجلب كائن Assembly
الخاص بصنف معين:
using System.Reflection; Assembly assembly = this.GetType().Assembly;
اِستخدِم الشيفرة التالية لجَلْب كائن Assembly
الخاص بالشيفرة قيد التنفيذ:
Assembly assembly = Assembly.GetExecutingAssembly();
يُوفِّر الصنف Assembly
التابع GetTypes
المُستخدَم لجَلْب قائمة بجميع الأصناف المُعرَّفة ضِمْن التجميعة:
foreach (var type in assembly.GetTypes()) { Console.WriteLine(type.FullName); }
موازنة كائنين باستخدام الانعكاس
في المثال التالي، يُستخَدم الانعكاس لموازنة كائنين. بالتحديد، يُستخدم التابع GetType
لجلْب قيمة من الصنف Type
تُحدد نوع الكائن، والتي بدورها تُستخدَم لجلْب قائمة بحقول الكائن باستدعاء التابع GetFields
من خلالها، ثم يتم موازنة قيم تلك الحقول مع نظيراتها بالكائن الآخر.
public class Equatable { public string field1; public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; var type = obj.GetType(); if (GetType() != type) return false; var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var field in fields) if (field.GetValue(this) != field.GetValue(obj)) return false; return true; } public override int GetHashCode() { var accumulator = 0; var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var field in fields) accumulator = unchecked ((accumulator * 937) ^ field.GetValue(this).GetHashCode()); return accumulator; } }
لاحظ أنه بغرض التبسيط، فإن المثال باﻷعلى يُجري موازنة معتمدة على الحقول فقط (يتجاهل الحقول الساكنة [static fields]، والخاصيات [properties]).
جَلْب سِمة تعداد (enum) باستخدام الانعكاس (وتخزينها بصورة مؤقتة caching)
تُعدّ السمات (attributes) مفيدة للإشارة إلى بعض البيانات الوَصفيّة (metadata) بالتعدادات (enums).
يُستخدَم عادة التابع GetCustomAttributes
لجَلْب قيم تلك السمات والذي قد يكون بطيئًا، لذلك من المهم الاستعانة بالذاكرة المخبئية لتخزين تلك القيم (caching)، كالتالي:
private static Dictionary<object, object> attributeCache = new Dictionary<object, object>(); public static T GetAttribute<T, V>(this V value) where T : Attribute where V : struct { object temp; // حاول جلب قيمة السمة من الذاكرة المخبئية أولًا if (attributeCache.TryGetValue(value, out temp)) { return (T) temp; } else { // اجلب النوع Type type = value.GetType(); FieldInfo fieldInfo = type.GetField(value.ToString()); // اجلب سمات هذا النوع T[] attribs = (T[])fieldInfo.GetCustomAttributes(typeof(T), false); // أعد أول سمة تجدها var result = attribs.Length > 0 ? attribs[0] : null; // خزن النتيجة بالذاكرة المخبئية attributeCache.Add(value, result); return result; } }
ضَبْط خواص الكائنات باستخدام الانعكاس
بفرض أن لدينا الصنف التالي Classy
الذي يَملك الخاصية Propertua
:
public class Classy { public string Propertua {get; set;} }
لضبْط الخاصية Propertua
الموجودة بكائن من النوع Classy
باستخدام الانعكاس، يمكن استخدام التابع SetValue
:
var typeOfClassy = typeof (Classy); var classy = new Classy(); var prop = typeOfClassy.GetProperty("Propertua"); prop.SetValue(classy, "Value");
تنشئة كائن من النوع T باستخدام الانعكاس
باستخدام باني الكائنات الافتراضي (default constructor):
T variable = Activator.CreateInstance(typeof(T));
باستخدام بَانِي ذات معاملات غير محدَّدة النوع (parameterized constructor):
T variable = Activator.CreateInstance(typeof(T), arg1, arg2);
الإطار المُدار القابل للتوسيع (MEF)
الإطار المُدار القَابِل للتوسيع Managed Extensibility Framework - MEF
هو مكتبة لإنشاء برامج صغيرة الحجم وقابلة للتوسيع.
عادةً ما تُسجَّل التَبَعيّات (dependencies) داخِل ملفات إعداد بالشيفرة المصدرية (hardcoding). يَترتَب على ذلك أنه لا يُصبِح بالإمكان تَغيير تلك التَبَعيّات إلا عن طريق تعديل الشيفرة وإعادة تَجمِيعها. على العكس من ذلك، يَسمَح الإطار المُدار القابل للتوسيع MEF
باكتشاف التَبَعيّات أثناء زمن التشغيل (runtime) ضِمْنِيًّا، واستخدامها دون إعداد مُسبَق.
يَسمَح MEF
لعِدة مُكَوِّنات (components) بالتواصل معًا بانسيابية وسهولة. يَستخدِم كل مُكَوِّن سِمات مُعينة (attributes) للإعلان عن تَبَعيّاته وقُدراته، أي ما يَحتاج إلى اِستيراده (imports) وما يقوم بتَصديره (exports)-إن وُجِدَ- على الترتيب. يُعلَّن عن كُلًا من الاستيرادات والتصديرات بصورة مُواصَفَة اِصطلاحيّة (contract). يَنبغي لمُصَدِّر ومُستورِد مُعينين الإعلان عن نفس المُواصَفَة الاصطلاحيّة لعَدِّهما نَظيرين. لاحظ أنه لمّا كانت كل هذه المَعلومات مُتوفِّرة بالبيانات الوصفية (metadata) للمُكَوِّن، أَصبَح مِن المُمكن اكتشافها أثناء زمن التشغيل (runtime).
يُزوِّد مُحرِك MEF
المُكَوِّنات باستيراداتها (imports) المُعلَّن عنها اعتمادًا على حَاوِي التركيب (composition container) الذي يَضُمّ كتالوجات (catalogs) تَشتمِل على معلومات عن جميع المُكَوِّنات المُصدَّرة والمُتاحة للتركيب.
تصدير صنف (Exporting)
يُمكن لأي مُكَوِّن استخدام السمة ExportAttribute
للاعلان عن تَصدير (export). في المثال التالي، صُدِّرَ النوع UserProvider
كمُحقِّق للمُواصَفَة الاصطلاحيّة IUserProvider
:
using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; namespace Demo { [Export(typeof(IUserProvider))] public sealed class UserProvider : IUserProvider { public ReadOnlyCollection<User> GetAllUsers() { return new List<User> { new User(0, "admin"), new User(1, "Dennis"), new User(2, "Samantha"), }.AsReadOnly(); } } }
في المثال بالأعلى، يُمكن تَعرِيف الصنف UserProvider
بأيّ مكان؛ فالمهم هو تَزْوِيد الكتالوج (ComposablePartCatalogs
) -الذي يُنشئه البرنامج- بطريقة يَستطيع مِن خلالها اكتشاف هذا الصنف.
استيراد صنف (Importing)
يُمكن لأي مُكَوِّن استخدَام السمة ImportAttribute
للاعلان عن استيراد (import) أو تَبَعيّة. انظر المثال التالي:
using System; using System.ComponentModel.Composition; namespace Demo { public sealed class UserWriter { [Import(typeof(IUserProvider))] private IUserProvider userProvider; public void PrintAllUsers() { foreach (User user in this.userProvider.GetAllUsers()) { Console.WriteLine(user); } } } }
في المثال بالأعلى، يُعلِّن الصنف UserWriter
عن استيراد لصنف يُحقِّق المُواصَفَة الاصطلاحيّة IUserProvider
كقيمة للحَقْل userProvider
. لاحِظ أنه ليس مُهمًا أين تقوم بتَعرِيف الصنف المُناظِر؛ فالمهم هو تَزْوِيد الكتالوج (ComposablePartCatalogs
) -الذي يُنشئه البرنامج- بطريقة يستطيع من خلالها اكتشاف هذا الصنف.
الرَبطْ (مثال بسيط)
using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; namespace Demo { public static class Program { public static void Main() { using (var catalog = new ApplicationCatalog()) using (var exportProvider = new CatalogExportProvider(catalog)) using (var container = new CompositionContainer(exportProvider)) { exportProvider.SourceProvider = container; UserWriter writer = new UserWriter(); // at this point, writer's userProvider field is null container.ComposeParts(writer); // now, it should be non-null (or an exception will be thrown). writer.PrintAllUsers(); } } } }
في المثال بالأعلى، تم تَزْوِيد حَاوِي التركيب (composition container) بكتالوج من النوع ApplicationCatalog
، والذي يَعتمِد -في بَحْثه عن التَصديرات المُناظِرة- على ملفات التجميعات بامتداد .exe
و DLL
الموجودة بمجلد البرنامج. لذلك طالما تَوَفَّرت السِمة [Export(typeof(IUserProvider))]
باحدى ملفات التجميعات بالمجلد، ستَنجَح عملية استيراد المُواصَفَة الاصطلاحيّة IUserProvider
المُعلَّن عنها بداخل الصنف UserWriter
.
تَتوفَّر أنواع أُخرى من الكتالوجات مثل DirectoryCatalog
، والتي يُمكِن استخدَامِها كبديل أو كإضافة للنوع ApplicationCatalog
لتَوسِيع دائرة البَحْث عن تَصدِيرات مُناظِرة للاستيرادات المطلوبة.
ترجمة -وبتصرف- للفصول Reflection - Managed Extensibility Framework - System.Reflection.Emit namespace من كتاب .NET Framework Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.