مقدمه
در درس قبل، دیدیم که چگونه میتوان با استفاده از انعکاس (Reflection)، فرادادهی (metadata) یک
نوع را در زمان اجرا بازرسی کرد. اما قدرت انعکاس فراتر از بازرسی صرف است. ما میتوانیم از
اطلاعاتی که به دست میآوریم برای تعامل دینامیک با آن نوع استفاده کنیم؛ یعنی نمونههایی از آن را
بسازیم، پراپرتیهایش را بخوانیم و تغییر دهیم، و متدهایش را فراخوانی کنیم، همهی اینها در حالیست
که در زمان کامپایل هیچ اطلاعاتی در مورد آن نوع نداریم. این فرآیند که به آن اتصال
دیرهنگام (Late Binding) گفته میشود، در مقابل اتصال زودهنگام (Early
Binding) قرار میگیرد. در اتصال زودهنگام (رویکرد عادی ما)، کامپایلر در زمان کامپایل
تمام انواع و اعضا را میشناسد و فراخوانیها را مستقیماً به آدرسهای حافظه متصل میکند. اما در
اتصال دیرهنگام، این تصمیمگیریها به زمان اجرا موکول میشود.
چرا به اتصال دیرهنگام نیاز داریم؟
اتصال دیرهنگام برای سناریوهایی طراحی شده که در آنها انعطافپذیری و پویایی در اولویت قرار دارد.
برخی از کاربردهای رایج عبارتند از:
- سیستمهای پلاگین (Plugin Architectures): برنامهی شما میتواند در زمان
اجرا، یک اسمبلی (.dll) را که از قبل نمیشناخته، بارگذاری کرده و
با استفاده از انعکاس، انواع موجود در آن را پیدا، نمونهسازی و استفاده کند.
- ابزارهای تست و چارچوبها: یک فریمورک تست میتواند یک اسمبلی را بازرسی
کرده، تمام متدهایی را که با یک صفت (attribute) خاص علامتگذاری شدهاند پیدا کرده و آنها را
به صورت دینامیک اجرا کند.
- اتوماسیون و اسکریپتنویسی: برنامههایی که به کاربران اجازه میدهند تا با
نوشتن اسکریپت، رفتار برنامه را تغییر دهند، اغلب از اتصال دیرهنگام برای اجرای آن اسکریپتها
استفاده میکنند.
ایجاد دینامیک یک نمونه
اولین قدم برای کار با یک نوع به صورت دینامیک، ایجاد یک نمونه از آن است. برای این کار، از کلاس
System.Activator استفاده میکنیم. متد CreateInstance این کلاس
میتواند یک شیء Type را به عنوان ورودی گرفته و یک نمونه از آن نوع را با فراخوانی سازندهی
پیشفرض و بدون پارامتر آن، ایجاد کند.
Program.cs
using System.Reflection;
string assemblyName = "System.Private.CoreLib";
string typeName = "System.DateTime";
Assembly asm = Assembly.Load(new AssemblyName(assemblyName));
Type type = asm.GetType(typeName);
object instance = Activator.CreateInstance(type);
Console.WriteLine($"Successfully created an instance of: {instance.GetType().FullName}");
در این مثال، ما نام اسمبلی و نوع را به صورت رشته داریم. ما اسمبلی را بارگذاری کرده، شیء Type
را از آن میگیریم و سپس با Activator.CreateInstance یک نمونه از آن میسازیم. توجه کنید که
متغیر instance از نوع object است، زیرا ما در زمان کامپایل نوع دقیق آن را نمیدانیم.
فراخوانی دینامیک یک متد
اکنون که یک نمونه از شیء را داریم، چگونه متدهای آن را فراخوانی کنیم؟ برای این کار، ابتدا باید یک
شیء MethodInfo که نمایندهی متد مورد نظر ماست را به دست آوریم. سپس
میتوانیم متد Invoke را بر روی آن MethodInfo فراخوانی کنیم.
متد Invoke دو پارامتر میگیرد:
- شیئی که میخواهیم متد را بر روی آن فراخوانی کنیم (همان instance که در مرحلهی قبل ساختیم).
- آرایهای از اشیاء (object[]) که پارامترهای مورد نیاز برای آن متد را شامل میشود.
Program.cs
public class SimpleMath
{
public int Add(int a, int b) => a + b;
}
Type mathType = typeof(SimpleMath);
object mathInstance = Activator.CreateInstance(mathType);
MethodInfo addMethod = mathType.GetMethod("Add");
object[] parameters = { 10, 20 };
object result = addMethod.Invoke(mathInstance, parameters);
Console.WriteLine($"Result of dynamic invocation: {result}");
در این کد، ما به صورت کاملاً دینامیک، بدون اینکه در کد خود به طور مستقیم به SimpleMath یا
Add اشاره کنیم، توانستیم متد Add را فراخوانی کرده و نتیجهی آن را دریافت کنیم.
اتصال دیرهنگام در مقابل کلمهی کلیدی dynamic
در C# 4.0، کلمهی کلیدی dynamic برای سادهسازی سناریوهای
اتصال دیرهنگام معرفی شد. وقتی شما یک متغیر را از نوع dynamic تعریف میکنید، کامپایلر
C# تمام بررسیهای نوع را برای آن متغیر تا زمان اجرا به تعویق میاندازد.
dynamic obj = Activator.CreateInstance(typeof(SimpleMath));
var result = obj.Add(10, 20);
در پشت صحنه، کامپایلر این کد را به کدی بسیار شبیه به کد انعکاس که ما به صورت دستی نوشتیم، تبدیل
میکند. استفاده از dynamic کد را بسیار تمیزتر میکند، اما باید با احتیاط استفاده شود، زیرا شما
تمام مزایای امنیت نوع در زمان کامپایل را از دست میدهید و هرگونه خطایی (مانند اشتباه تایپی در
نام متد) تنها در زمان اجرا مشخص خواهد شد.