مقدمه

در درس قبل، دیدیم که چگونه می‌توان با استفاده از انعکاس (Reflection)، فراداده‌ی (metadata) یک نوع را در زمان اجرا بازرسی کرد. اما قدرت انعکاس فراتر از بازرسی صرف است. ما می‌توانیم از اطلاعاتی که به دست می‌آوریم برای تعامل دینامیک با آن نوع استفاده کنیم؛ یعنی نمونه‌هایی از آن را بسازیم، پراپرتی‌هایش را بخوانیم و تغییر دهیم، و متدهایش را فراخوانی کنیم، همه‌ی این‌ها در حالیست که در زمان کامپایل هیچ اطلاعاتی در مورد آن نوع نداریم. این فرآیند که به آن اتصال دیرهنگام (Late Binding) گفته می‌شود، در مقابل اتصال زودهنگام (Early Binding) قرار می‌گیرد. در اتصال زودهنگام (رویکرد عادی ما)، کامپایلر در زمان کامپایل تمام انواع و اعضا را می‌شناسد و فراخوانی‌ها را مستقیماً به آدرس‌های حافظه متصل می‌کند. اما در اتصال دیرهنگام، این تصمیم‌گیری‌ها به زمان اجرا موکول می‌شود.

چرا به اتصال دیرهنگام نیاز داریم؟

اتصال دیرهنگام برای سناریوهایی طراحی شده که در آن‌ها انعطاف‌پذیری و پویایی در اولویت قرار دارد. برخی از کاربردهای رایج عبارتند از:

  • سیستم‌های پلاگین (Plugin Architectures): برنامه‌ی شما می‌تواند در زمان اجرا، یک اسمبلی (.dll) را که از قبل نمی‌شناخته، بارگذاری کرده و با استفاده از انعکاس، انواع موجود در آن را پیدا، نمونه‌سازی و استفاده کند.
  • ابزارهای تست و چارچوب‌ها: یک فریم‌ورک تست می‌تواند یک اسمبلی را بازرسی کرده، تمام متدهایی را که با یک صفت (attribute) خاص علامت‌گذاری شده‌اند پیدا کرده و آن‌ها را به صورت دینامیک اجرا کند.
  • اتوماسیون و اسکریپت‌نویسی: برنامه‌هایی که به کاربران اجازه می‌دهند تا با نوشتن اسکریپت، رفتار برنامه را تغییر دهند، اغلب از اتصال دیرهنگام برای اجرای آن اسکریپت‌ها استفاده می‌کنند.

ایجاد دینامیک یک نمونه

اولین قدم برای کار با یک نوع به صورت دینامیک، ایجاد یک نمونه از آن است. برای این کار، از کلاس System.Activator استفاده می‌کنیم. متد CreateInstance این کلاس می‌تواند یک شیء Type را به عنوان ورودی گرفته و یک نمونه از آن نوع را با فراخوانی سازنده‌ی پیش‌فرض و بدون پارامتر آن، ایجاد کند.

Copy Icon Program.cs
using System.Reflection;

// Assume we get the name of an assembly and a type from a config file.
string assemblyName = "System.Private.CoreLib";
string typeName = "System.DateTime";

// Load the assembly.
Assembly asm = Assembly.Load(new AssemblyName(assemblyName));

// Get the Type object.
Type type = asm.GetType(typeName);

// Create an instance of the type dynamically.
// This calls the default parameterless constructor.
object instance = Activator.CreateInstance(type);

Console.WriteLine($"Successfully created an instance of: {instance.GetType().FullName}");

در این مثال، ما نام اسمبلی و نوع را به صورت رشته داریم. ما اسمبلی را بارگذاری کرده، شیء Type را از آن می‌گیریم و سپس با Activator.CreateInstance یک نمونه از آن می‌سازیم. توجه کنید که متغیر instance از نوع object است، زیرا ما در زمان کامپایل نوع دقیق آن را نمی‌دانیم.

فراخوانی دینامیک یک متد

اکنون که یک نمونه از شیء را داریم، چگونه متدهای آن را فراخوانی کنیم؟ برای این کار، ابتدا باید یک شیء MethodInfo که نماینده‌ی متد مورد نظر ماست را به دست آوریم. سپس می‌توانیم متد Invoke را بر روی آن MethodInfo فراخوانی کنیم.

متد Invoke دو پارامتر می‌گیرد:

  1. شیئی که می‌خواهیم متد را بر روی آن فراخوانی کنیم (همان instance که در مرحله‌ی قبل ساختیم).
  2. آرایه‌ای از اشیاء (object[]) که پارامترهای مورد نیاز برای آن متد را شامل می‌شود.
Copy Icon Program.cs
public class SimpleMath
{
    public int Add(int a, int b) => a + b;
}

// --- In Main method ---
Type mathType = typeof(SimpleMath);
object mathInstance = Activator.CreateInstance(mathType);

// 1. Get the MethodInfo for the "Add" method.
MethodInfo addMethod = mathType.GetMethod("Add");

// 2. Prepare the parameters.
object[] parameters = { 10, 20 };

// 3. Invoke the method on the instance with the parameters.
object result = addMethod.Invoke(mathInstance, parameters);

Console.WriteLine($"Result of dynamic invocation: {result}"); // Output: 30

در این کد، ما به صورت کاملاً دینامیک، بدون اینکه در کد خود به طور مستقیم به SimpleMath یا Add اشاره کنیم، توانستیم متد Add را فراخوانی کرده و نتیجه‌ی آن را دریافت کنیم.

اتصال دیرهنگام در مقابل کلمه‌ی کلیدی dynamic

در C# 4.0، کلمه‌ی کلیدی dynamic برای ساده‌سازی سناریوهای اتصال دیرهنگام معرفی شد. وقتی شما یک متغیر را از نوع dynamic تعریف می‌کنید، کامپایلر C# تمام بررسی‌های نوع را برای آن متغیر تا زمان اجرا به تعویق می‌اندازد.

dynamic obj = Activator.CreateInstance(typeof(SimpleMath));
var result = obj.Add(10, 20); // Works!

در پشت صحنه، کامپایلر این کد را به کدی بسیار شبیه به کد انعکاس که ما به صورت دستی نوشتیم، تبدیل می‌کند. استفاده از dynamic کد را بسیار تمیزتر می‌کند، اما باید با احتیاط استفاده شود، زیرا شما تمام مزایای امنیت نوع در زمان کامپایل را از دست می‌دهید و هرگونه خطایی (مانند اشتباه تایپی در نام متد) تنها در زمان اجرا مشخص خواهد شد.