مقدمه

زبان C# در هسته‌ی خود یک زبان با نوع‌دهی ایستا (Statically Typed) است. این یعنی نوع تمام متغیرها، پارامترها و مقادیر بازگشتی باید در زمان کامپایل مشخص باشد و کامپایلر این انواع را به شدت بررسی می‌کند. این ویژگی باعث افزایش امنیت، کارایی و قابلیت نگهداری کد می‌شود. با این حال، دنیای نرم‌افزار همیشه ایستا نیست. ما اغلب نیاز داریم تا با سیستم‌هایی که ماهیت پویا (dynamic) دارند، مانند زبان‌های اسکریپت‌نویسی (مثل پایتون و جاوااسکریپت) یا فرمت‌های داده‌ای مانند JSON، تعامل داشته باشیم.

در درس‌های قبل دیدیم که چگونه می‌توان با استفاده از انعکاس (Reflection) و اتصال دیرهنگام (Late Binding)، به صورت پویا با انواع کار کرد. اما این روش می‌تواند طولانی و پیچیده باشد. برای ساده‌سازی این سناریوها، C# 4.0 کلمه‌ی کلیدی dynamic و یک زیرساخت جدید به نام Dynamic Language Runtime (DLR) را معرفی کرد. این ویژگی‌ها به C# اجازه می‌دهند تا در کنار ماهیت ایستای خود، از قابلیت‌های برنامه‌نویسی دینامیک نیز بهره‌مند شود.

کلمه‌ی کلیدی dynamic

کلمه‌ی کلیدی dynamic به کامپایلر می‌گوید: "من به تو اطمینان می‌دهم که عملیاتی که روی این متغیر انجام می‌دهم، در زمان اجرا معتبر خواهد بود. لطفاً هیچ‌گونه بررسی نوعی را در زمان کامپایل برای آن انجام نده و این کار را به زمان اجرا موکول کن."

وقتی یک متغیر را از نوع dynamic تعریف می‌کنید، شما می‌توانید هر متد یا پراپرتی را بر روی آن فراخوانی کنید، گویی که آن عضو حتماً وجود دارد. کامپایلر این کد را بدون خطا می‌پذیرد. سپس در زمان اجرا، DLR وارد عمل شده و تلاش می‌کند تا آن عضو را بر روی شیء واقعی پیدا و اجرا کند. اگر عضو مورد نظر وجود داشته باشد، کد با موفقیت اجرا می‌شود. اگر وجود نداشته باشد، یک استثناء در زمان اجرا (runtime exception) پرتاب خواهد شد.

Copy Icon Program.cs
// The compiler doesn't know the type of 'd', so it skips type checking.
dynamic d = "Hello, World!";

// This call is valid because string has a Length property.
Console.WriteLine(d.Length);

// The compiler allows this, but it will fail at RUNTIME.
// A 'RuntimeBinderException' will be thrown because string doesn't have a 'NonExistentMethod'.
try
{
    d.NonExistentMethod();
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
    Console.WriteLine(ex.Message);
}

// You can even change the underlying type of a dynamic variable.
d = 100;
Console.WriteLine(d + 50); // Now it behaves like an integer.

این مثال به وضوح قدرت و در عین حال خطر dynamic را نشان می‌دهد. ما از تمام مزایای بررسی زمان کامپایل صرف‌نظر کرده‌ایم و مسئولیت صحت فراخوانی‌ها را به زمان اجرا منتقل کرده‌ایم.

مقایسه‌ی dynamic و object

در نگاه اول، ممکن است dynamic شبیه به object به نظر برسد، زیرا هر دو می‌توانند هر نوع داده‌ای را در خود نگه دارند. اما یک تفاوت بسیار بنیادی بین آن‌ها وجود دارد:

  • System.Object: این نوع، ایستا (static) است. کامپایلر می‌داند که نوع متغیر object است. برای دسترسی به اعضای خاص نوع واقعی که در آن ذخیره شده، شما باید آن را به صورت صریح به آن نوع تبدیل (cast) کنید. اگر تبدیل نامعتبر باشد، کامپایلر (در صورت امکان) یا CLR (در زمان اجرا) خطا می‌دهد.
  • dynamic: این نوع، پویا (dynamic) است. کامپایلر وانمود می‌کند که نوع متغیر را نمی‌داند. شما می‌توانید هر عضوی را مستقیماً بر روی آن فراخوانی کنید بدون نیاز به تبدیل نوع. بررسی صحت این فراخوانی به طور کامل به زمان اجرا موکول می‌شود.
Copy Icon Program.cs
object obj = "A string in an object";
dynamic dyn = "A string in a dynamic";

// To access 'Length' on 'obj', we MUST cast it first.
int length1 = ((string)obj).Length;

// This line would cause a compile-time error because 'object' doesn't have a 'Length' property.
// int length_error = obj.Length;

// With 'dynamic', no cast is needed. The call is resolved at runtime.
int length2 = dyn.Length;

Console.WriteLine($"Length via object: {length1}");
Console.WriteLine($"Length via dynamic: {length2}");

کاربردهای اصلی برنامه‌نویسی دینامیک

با توجه به از دست رفتن امنیت نوع، باید از dynamic با احتیاط و فقط در سناریوهایی که واقعاً به آن نیاز است، استفاده کنیم.

۱. تعامل با زبان‌های پویا

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

۲. ساده‌سازی کد انعکاس (Reflection)

در درس قبل دیدیم که فراخوانی دینامیک یک متد با استفاده از انعکاس می‌تواند طولانی و پیچیده باشد. dynamic می‌تواند این کد را به شدت ساده کند.

Copy Icon Program.cs
public class MyClass { public void DoSomething(string message) => Console.WriteLine(message); }

// --- Using Reflection ---
Type myType = typeof(MyClass);
object instance1 = Activator.CreateInstance(myType);
MethodInfo method = myType.GetMethod("DoSomething");
method.Invoke(instance1, new object[] { "Hello via Reflection" });

// --- Using dynamic (much simpler!) ---
dynamic instance2 = Activator.CreateInstance(myType);
instance2.DoSomething("Hello via dynamic");

۳. کار با داده‌های JSON

یکی از کاربردهای بسیار رایج، کار با داده‌های JSON است. کتابخانه‌هایی مانند Newtonsoft.Json به شما اجازه می‌دهند تا یک رشته‌ی JSON را به یک شیء dynamic تبدیل کنید و به راحتی به پراپرتی‌های تودرتوی آن دسترسی پیدا کنید، بدون اینکه نیاز به تعریف کلاس‌های متناظر داشته باشید.

Copy Icon Program.cs
using Newtonsoft.Json;

string json = @"{ 'Name': 'John Doe', 'Address': { 'City': 'New York' } }";
dynamic data = JsonConvert.DeserializeObject(json);

Console.WriteLine($"Name: {data.Name}, City: {data.Address.City}");