تواناییهای Reflection.Emit صرفا به ایجاد متدهایی کاملا جدید و پویا در زمان اجرا محدود نمیشود. برای نمونه کلاس ذیل را درنظر بگیرید:
در ادامه قصد داریم معادل این کلاس را به همراه وهلهای از آن، به صورتی کاملا پویا در زمان اجرا ایجاد کرده (تصور کنید این کلاس در برنامه وجود خارجی نداشته و تنها جهت درک بهتر کدهای IL ادامه بحث، معرفی گردیده است) و سپس مقداری را به سازنده آن ارسال کنیم.
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه میکنید:
در اینجا ایجاد یک کلاس جدید با ایجاد یک TypeBuilder واقع در فضای نام System.Reflection.Emit آغاز میشود. پیش از آن نیاز است یک اسمبلی پویا و ماژولی در آنرا برای قرار دادن کدهای پویای این TypeBuilder ایجاد کنیم. توضیحات مرتبط با دستورات مختلف را به صورت کامنت در کدهای فوق ملاحظه میکنید. با استفاده از TypeBuilder و متد DefineField آن میتوان یک فیلد در سطح کلاس ایجاد کرد و یا توسط متد DefineConstructor آن، سازنده کلاس را با امضایی ویژه تعریف نمود و سپس با دسترسی به ILGenerator آن، بدنه این سازنده را همانند متدهای پویا ایجاد کرد.
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کردهاید. تمام خواص Auto implemented در دات نت، هر چند ظاهر سادهای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل میشوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.
چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit
public class Person { private string _name; public string Name { get { return _name; } } public Person(string name) { _name = name; } }
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه میکنید:
using System; using System.Reflection; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static void Main(string[] args) { //اسمبلی محل قرارگیری کدهای پویای نهایی در اینجا تعیین میشود //حالت دسترسی به آن اجرایی درنظر گرفته شده، امکان تعیین حالتهای دیگری مانند ذخیره سازی نیز وجود دارد var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( name: new AssemblyName("Demo"), access: AssemblyBuilderAccess.Run); // اکنون داخل این اسمبلی یک ماژول جدید را برای قرار دادن کلاس جدید خود تعریف میکنیم var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: "PersonModule"); // کار ساخت نوع و کلاس جدید شخص عمومی از اینجا شروع میشود var typeBuilder = moduleBuilder.DefineType(name: "Person", attr: TypeAttributes.Public); // افزودن فیلد خصوصی نام تعریف شده در سطح کلاس شخص var nameField = typeBuilder.DefineField(fieldName: "_name", type: typeof(string), attributes: FieldAttributes.Private); // تعریف سازنده عمومی کلاس شخص که دارای یک آرگومان رشتهای است var ctor = typeBuilder.DefineConstructor( attributes: MethodAttributes.Public, callingConvention: CallingConventions.Standard, parameterTypes: new[] { typeof(string) }); // تعریف بدنه سازنده کلاس شخص // در اینجا فیلد خصوصی تعریف شده در سطح کلاس باید مقدار دهی شود var ctorIL = ctor.GetILGenerator(); // نکتهای در مورد سازندهها ctorIL.Emit(OpCodes.Ldarg_0); // اندیس صفر در سازنده کلاس به وهلهای از کلاس جاری اشاره میکند ctorIL.Emit(OpCodes.Ldarg_1); // بارگذاری آرگومان سازنده و قرار دادن آن روی پشته // مقدار دهی فیلد خصوصی نام که به وهلهای از کلاس جاری و مقدار آرگومان دریافتی نیاز دارد ctorIL.Emit(OpCodes.Stfld, nameField); ctorIL.Emit(OpCodes.Ret); // پایان کار سازنده // تعریف خاصیت رشتهای نام در کلاس شخص var nameProperty = typeBuilder.DefineProperty( name: "Name", attributes: PropertyAttributes.HasDefault, returnType: typeof(string), parameterTypes: null); // خاصیت پارامتر ورودی ندارد var namePropertyGetMethod = typeBuilder.DefineMethod( name: "get_Name", attributes: MethodAttributes.Public | //متد ویژهای است که توسط کامپایلر پردازش و تشخیص داده میشود MethodAttributes.SpecialName | MethodAttributes.HideBySig, returnType: typeof(string), parameterTypes: Type.EmptyTypes); // اتصال گت متد به خاصیت رشتهای نام که پیشتر تعریف شد nameProperty.SetGetMethod(namePropertyGetMethod); // بدنه گت متد در اینجا تعریف خواهد شد var namePropertyGetMethodIL = namePropertyGetMethod.GetILGenerator(); namePropertyGetMethodIL.Emit(OpCodes.Ldarg_0); // بارگذاری اشارهگری به وهلهای از کلاس جاری در پشته namePropertyGetMethodIL.Emit(OpCodes.Ldfld, nameField); // بارگذاری فیلد نام namePropertyGetMethodIL.Emit(OpCodes.Ret); var t = typeBuilder.CreateType(); // نهایی سازی کار ایجاد نوع جدید // ایجاد وهلهای از نوع جدید که پارامتری رشتهای به سازنده آن ارسال میشود var instance = Activator.CreateInstance(t, "Vahid"); // دسترسی به خاصیت نام var nProperty = t.GetProperty("Name"); // و دریافت مقدار آن برای نمایش var result = nProperty.GetValue(instance, null); Console.WriteLine(result); } } }
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کردهاید. تمام خواص Auto implemented در دات نت، هر چند ظاهر سادهای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل میشوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.
چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit