در این مقاله یکی از ویژگیهای جذاب و کاربردی چهارچوب دات نت یا.net framework را که مکانیزم شرح نوع یا Type Description است بررسی میکنیم. این مکانیزم در فضای نام System.ComponentModel قرار دارد و عموما توسط کلاسهای TypeDescriptor, CustomTypeDescriptor ، اینترفیس ICustomTypeDescriptor و برخی کلاسهای دیگر بیان میشود. این کلاسها عموما توسط برخی کنترلها مانند DataGrids و DataBinder مورد استفاده قرار میگیرند.
به طور خلاصه میتوانیم اینگونه بگوییم که این کلاسها به ما امکان میدهد تا یک نوع داده ای یا یک منبع داده ای تعریف کنیم که زمانی که به یک کنترل دات نت مقید سازی میشود – به عنوان مثال توسط عبارتی نظیر<%# %> - منجر به فراخوانی های انعکاسی ( reflection ) نشود. این کار بوسیله مشخص کردن اطلاعات ویژگیها به صورت پویا در زمان اجرا انجام میگردد.
بگذارید یک مثال عملی بزنیم. در پروژه که پیش رو داریم، کدی داریم که خروجی اش از نوع object[][] میباشد و حاوی مقادیری متادیتا مانند ستونها، طول، در دسترس بودن صفحات بعدی و .. است. برای نمایش این اطلاعات در یک صفحه ASPX یک روش بسیار بد این است که از دستوری مشابه <%# ((object[])DataItem)[3][4]%> استفاده نماییم !
کاری که من میخواهم انجام دهم این است که کلاسی داشته باشیم که حاوی اطلاعات جدول مانند به همراه برخی متادیتا باشد. همچنین نیاز داریم تا این کلاس قابلیت مقید سازی را مشابه آنچه که در DataTable داریم داشته باشد. یعنی مثلا بتوانیم از آن اینگونه استفاده کنیم:
<%# Eval(“ColumnName”) %>
قبل از هر چیزی، تصور میکردم ایجاد یک کلاس با یک ویژگی ایندکس شده میتواند کافی به نظر برسد. اما در این صورت مجبوریم از کدی شبیه به <%# Eval(“[ColumnName]”) %> استفاده کنیم که بدون کروشه ها اصلا کار نمیکند. اگرچه این راه حل قابل انجام است اما نظر من این است که اگر DataTable توانسته است به شکلی که در ابتدا گفته شد عمل مقید سازی را انجام دهد پس ما هم باید بتوانیم! در چنین حالتی است که کلاس DataBinder و متد GetPropertyValue میتوانند به کمک ما بشتابند.
PropertyDescriptor descriptor1 =
TypeDescriptor.GetProperties(container).Find(propName, true);
//omitted check for clarity
return descriptor1.GetValue(container);
چنانچه به مستندات MSDN نگاهی بیندازیم متوجه میشویم که کلاس DataTable اینترفیس ICustomTypeDescriptor رابه گونه ای شبیه به این پیاده سازی کرده است. البته این کد برداشت من از مستندات MSDN است.
//”Row” class
public class DataRetriverResultRow: ICustomTypeDescriptor
{
// Parent “DataTable” class
private DataRetriverResultSet proxy;
// other code omited
public override PropertyDescriptorCollection GetProperties()
{
return proxy.GetPropertyDescriptorCollection(null);
}
}
و آنجاست که در کلاس DataTable اطلاعات واقعی ویژگیها ایجاد میشود. چرا که همه سطرهای یک DataTable ویژگیهای یکسان دارند :
internal PropertyDescriptorCollection
GetPropertyDescriptorCollection(Attribute[] attributes)
{
if (this.propertyDescriptorCollectionCache == null)
{
int colCount = this.columns.Length;
PropertyDescriptor[] descriptors = new PropertyDescriptor[colCount];
for (int i = 0; i < colCount; i++)
{
descriptors[i] = new DataRetriverResultRowPropertyDescriptor(i,
this.columns[i]);
}
this.propertyDescriptorCollectionCache =
new PropertyDescriptorCollection(descriptors);
}
return this.propertyDescriptorCollectionCache;
}
و در نهایت PropertyDescriptor ( کلاسی که در حقیقت یک ویژگی را توصیف میکند و میداند که چگونه یک مقدار را از آن بگیرد) که با آن بعنوان یک متغیر سفارشی از کلاس PropertyInfo رفتار میکند:
private class DataRetriverResultRowPropertyDescriptor : PropertyDescriptor
{
int columnIndex;
public DataRetriverResultRowPropertyDescriptor(int index,
string name)
: base(name, null)
{
this.columnIndex = index;
}
// some dummy code implementing PropertyDescriptor ommited
public override Type ComponentType
{
get { return typeof(DataRetriverResultRow); }
}
// component – is the object containing
// the property, wich we are describing.
// So we can safely cast it and use it’s method to retrieve
// a value without any reflection.
public override object GetValue(object component)
{
return ((DataRetriverResultRow)
component).GetValue(columnIndex);
}
}
به همین سادگی! اساسا کاری که ما انجام دادیم این بود که برخی از انواع meta descriptor را ایجاد کردیم که اطلاعات ویژگیها را در زمان اجرا ایجاد مینمایند. و این کار را با استفاده از داده های پویا انجام دادیم. در این مثال داده های پویای ما شماره و ترتیب ستونها در DataTable بود.
اکنون زمانی که DataBinder.Eval سطر داده ها را از شیء ما میگیرد، از یک PropertyDescriptorCollection که به صورت سفارشی ایجاد و کش شده است بهره میبرد و به جای استفاده از متد PropertyInfo.GetValue ، از متد GetValue ما استفاده میکند. این کار علاوه بر آن که بازدهی بهتری را به ارمغان می آورد، باعث این میشود که بتوانیم از سینتکس خلاصه تر و زیباتری استفاده کنیم.