مقدمه
در درس قبل، دیدیم که چگونه فراداده (Metadata) اطلاعات کاملی را در مورد انواع داده و اسمبلیها در
.NET ذخیره میکند. انعکاس (Reflection)، API یا مجموعهای از کلاسها
در فریمورک .NET است که به ما اجازه میدهد تا در زمان اجرا، این
فراداده را بخوانیم و از آن برای بازرسی و دستکاری دینامیک انواع، اعضا و اسمبلیها استفاده کنیم.
با انعکاس، یک برنامه میتواند اطلاعاتی در مورد خودش (یا اسمبلیهای دیگر) به دست آورد و بر اساس
آن اطلاعات، کارهایی را انجام دهد. این قابلیت، پایهی اصلی بسیاری از فریمورکهای پیشرفته و
ابزارهای توسعه است و به ما امکان میدهد برنامههایی بسیار پویا و انعطافپذیر بسازیم.
System.Type: دروازهی ورود به دنیای Reflection
نقطهی شروع کار با انعکاس، کلاس System.Type است. یک شیء از نوع
Type، نمایندهی فرادادهی یک نوع دادهی خاص (مانند یک کلاس، ساختار، یا اینترفیس) در زمان
اجراست. هر نوع دادهای که در C# وجود دارد، یک شیء Type متناظر با خود دارد.
چگونه یک شیء Type به دست آوریم؟
چندین راه برای به دست آوردن شیء Type یک نوع وجود دارد:
- استفاده از متد GetType(): همانطور که در درس مربوط به کلاس Object دیدیم،
هر شیء در .NET متد GetType() را به ارث میبرد. این متد، شیء Type مربوط به نوع
واقعی آن شیء در زمان اجرا را برمیگرداند.
- استفاده از عملگر typeof: اگر نام نوع را در زمان کامپایل میدانید،
میتوانید از عملگر typeof برای گرفتن شیء Type آن استفاده کنید.
- استفاده از متد استاتیک Type.GetType(): این متد نام کامل نوع را به صورت یک
رشته دریافت کرده و تلاش میکند تا شیء Type متناظر با آن را پیدا و برگرداند.
Program.cs
using System.Reflection;
DateTime dt = DateTime.Now;
Type type1 = dt.GetType();
Console.WriteLine($"Type from GetType(): {type1.Name}");
Type type2 = typeof(DateTime);
Console.WriteLine($"Type from typeof: {type2.Name}");
Type type3 = Type.GetType("System.Int32");
Console.WriteLine($"Type from GetType(string): {type3.Name}");
بازرسی اعضای یک نوع
پس از اینکه یک شیء Type را به دست آوردیم، میتوانیم از متدهای متعدد آن برای بازرسی فرادادهی
اعضای آن نوع استفاده کنیم. این متدها معمولاً به صورت جفت هستند: یک متد برای گرفتن یک عضو خاص با
نام مشخص، و یک متد برای گرفتن آرایهای از تمام اعضای یک نوع خاص.
بیایید یک کلاس نمونه بسازیم و سپس با استفاده از انعکاس، اعضای آن را بازرسی کنیم.
Car.cs
public class Car
{
public string Model { get; set; }
private int _speed;
public Car(string model) { Model = model; }
public void Accelerate(int amount) { _speed += amount; }
}
حالا میتوانیم فرادادهی این کلاس را بخوانیم:
Program.cs
Type carType = typeof(Car);
Console.WriteLine($"--- Inspecting {carType.Name} ---");
MethodInfo[] methods = carType.GetMethods();
Console.WriteLine("\nPublic Methods:");
foreach (MethodInfo method in methods)
{
Console.WriteLine(method.Name);
}
PropertyInfo[] properties = carType.GetProperties();
Console.WriteLine("\nPublic Properties:");
foreach (PropertyInfo prop in properties)
{
Console.WriteLine(prop.Name);
}
ConstructorInfo[] constructors = carType.GetConstructors();
Console.WriteLine("\nPublic Constructors:");
foreach (ConstructorInfo ctor in constructors)
{
Console.WriteLine(ctor.Name);
}
خروجی این کد، لیستی از تمام اعضای عمومی کلاس Car (به علاوهی متدهایی که از System.Object به
ارث برده) را نمایش میدهد.
دسترسی به اعضای غیرعمومی
متدهای GetMethods، GetProperties و ... به طور پیشفرض فقط اعضای public را برمیگردانند.
برای دسترسی به اعضای private، protected یا داخلی، باید از یک نسخهی سربارگذاری شدهی این
متدها استفاده کنیم که یک BindingFlags را به عنوان پارامتر میپذیرد.
Program.cs
FieldInfo privateField = carType.GetField("_speed",
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"\nFound private field: {privateField.Name}");
هزینهی عملکردی انعکاس
انعکاس ابزاری بسیار قدرتمند است، اما این قدرت بدون هزینه نیست. عملیات انعکاس به طور قابل توجهی
کندتر از دسترسی مستقیم به اعضا در زمان کامپایل است. این به این دلیل است که به
جای یک دسترسی مستقیم به حافظه، مجموعهای از جستجوها و بررسیها در جداول فراداده انجام میشود.
بنابراین، باید از انعکاس با احتیاط استفاده کرد. از آن برای کارهایی که در زمان راهاندازی برنامه
یا به ندرت انجام میشوند (مانند بارگذاری پلاگینها یا سیستمهای تزریق وابستگی) استفاده کنید و از
به کار بردن آن در حلقههای پرتکرار یا مسیرهای حیاتی از نظر عملکردی برنامه، خودداری نمایید.