۱۳۹۰/۰۵/۰۹

Static Reflection


قابليت Dynamic reflection يا به اختصار همان reflection متداول، از اولين نگارش‌هاي دات نت فريم در دسترس است و امكان دسترسي به اطلاعات مرتبط با كلاس‌ها، متدها، خواص و غيره را در زمان اجرا مهيا مي‌سازد. تابحال به كمك اين قابليت، امكان تهيه‌ي ابزارهاي پيشرفته‌ي زير مهيا شده است:
انواع و اقسام
- فريم ورك‌هاي آزمون واحد
- code generators
- ORMs
- ابزارهاي آناليز كد
و ...


براي مثال فرض كنيد كه مي‌خواهيد براي يك كلاس به صورت خودكار، متدهاي آزمون واحد تهيه كنيد (تهيه يك code generator ساده). اولين نياز اين برنامه، دسترسي به امضاي متدها به همراه نام آرگومان‌ها و نوع آن‌ها است. براي حل اين مساله بايد براي مثال يك parser زبان سي شارپ يا اگر بخواهيد كامل‌تر كار كنيد، به ازاي تمام زبان‌هاي قابل استفاده در دات نت فريم ورك بايد parser تهيه كنيد كه ... كار ساده‌اي نيست. اما با وجود reflection به سادگي مي‌توان به اين نوع اطلاعات دسترسي پيدا كرد و نكته‌ي مهم آن هم اين است كه مستقل است از نوع زبان مورد استفاده. به همين جهت است كه اين نوع ابزارها را در فريم ورك‌هايي كه فاقد امكانات reflection هستند، كمتر مي‌توان يافت. براي مثال كيفيت كتابخانه‌هاي آزمون واحد CPP در مقايسه با آنچه كه در دات نت مهيا هستند، اصلا قابل مقايسه نيستند. براي نمونه به يكي از معظم‌ترين فريم ورك‌هاي آزمون واحد CPP كه توسط گوگل تهيه شده مراجعه كنيد : (+)
قابليت Reflection ، مطلب جديدي نيست و براي مثال زبان جاوا هم سال‌ها است كه از آن‌ پشتيباني مي‌كند. اما نگارش سوم دات نت فريم ورك با معرفي lambda expressions ، LINQ و Expressions در يك سطح بالاتر از اين Dynamic reflection متداول قرار گرفت.

تعريف Static Reflection :
استفاده از امكانات Reflection API بدون بكارگيري رشته‌ها، به كمك قابليت اجراي به تعويق افتاده‌ي LINQ، جهت دسترسي به متاديتاي المان‌هاي كد، مانند خواص، متدها و غيره.
براي مثال كد زير را در نظر بگيريد:
//dynamic reflection
PropertyInfo property = typeof (MyClass).GetProperty("Name");
MethodInfo method = typeof (MyClass).GetMethod("SomeMethod");
اين كد، يك نمونه از دسترسي به متاديتاي خواص يا متدها را به كمك Reflection متداول نمايش مي‌دهد. مهم‌ترين ايراد آن استفاده از رشته‌ها است كه تحت نظر كامپايلر نيستند و تنها زمان اجرا است كه مشخص مي‌شود آيا MyClass واقعا خاصيتي به نام Name داشته است يا خير.
چقدر خوب مي‌شد اگر اين قابليت بجاي dynamic بودن (مشخص شدن در زمان اجرا)، استاتيك مي‌بود و در زمان كامپايل قابل بررسي مي‌شد. اين امكان به كمك lambda expressions و expression trees دات نت سه بعد، ميسر شده است. كليدهاي اصلي Static Reflection كلاس‌هاي Func و Expression هستند. با استفاده از كلاس Func مي‌توان lambda expression ايي را تعريف كرد كه مقداري را بر مي‌گرداند و توسط كلاس Expression مي‌توان به محتواي يك delegate دسترسي يافت. تركيب اين دو، قدرت دستيابي به اطلاعاتي مانند PropertyInfo را در زمان طراحي كلاس‌ها، مي‌دهد؛ با توجه به اينكه:
- كاملا توسط intellisense موجود در VS.NET پشتيباني مي‌شود.
- با استفاده از ابزارهاي refactoring قابل كنترل است.
- از همه مهم‌تر، ديگري خبري از رشته‌ها نبوده و همه چيز تحت كنترل كامپايلر قرار مي‌گيرد.

و شايد هيچ قابليتي به اندازه‌ي Static Reflection در اين چندسال اخير بر روي اكوسيستم دات نت فريم ورك تاثيرگذار نبوده باشد. اين روزها كمتر كتابخانه يا فريم وركي را مي‌توانيد پيدا كنيد كه از Static Reflection استفاده نكند. سرآغاز استفاده گسترده از آن به Fluent NHibernate بر مي‌گردد؛ سپس در انواع و اقسام mocking frameworks‌ ، ORMs و غيره استفاده شد و مدتي است كه در ASP.NET MVC نيز مورد استفاده قرار مي‌گيرد (براي مثال TextBoxFor معروف آن):
public string TextBoxFor<T>(Expression<Func<T,object>> expression);
به اين ترتيب حين استفاده از آن ديگري نيازي نخواهد بود تا نام خاصيت مدل مورد نظر را به صورت رشته وارد كرد:
<%= this.TextBoxFor(model => model.FirstName); %>

يك مثال ساده از تعريف و بكارگيري Static Reflection :
public PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;

if (memberExpression == null)
throw new InvalidOperationException("Not a member access.");

return memberExpression.Member as PropertyInfo;
}
همانطور كه عنوان شد كليدهاي اصلي بهر‌ه‌گيري از امكانات Static reflection ، استفاده از كلاس‌هاي Expression و Func هستند كه در آرگومان متد فوق بكارگرفته شده‌اند و در حقيقت يك expression of a delegate است كه به آن Lambdas as Data نيز گفته مي‌شود. اين delegate پارامتري از نوع T را دريافت كرده و سپس مقداري از نوع object را بر مي‌گرداند. اما زمانيكه از كلاس Expression در اينجا استفاده مي‌شود، اين Func ديگر اجرا نخواهد شد، بلكه از آن به عنوان قطعه‌ كدي كه اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده مي‌شود.
براي نمونه Fluent NHibernate‌ در پشت صحنه متد Map ، به كمك متدي شبيه به GetProperty فوق، a => a.Address1 را به رشته متناظر خاصيت Address1 تبديل كرده و جهت تعريف نگاشت‌ها مورد استفاده قرار مي‌دهد:
public class AddressMap : DomainMap<Address>
{
public AddressMap()
{
Map(a => a.Address1);
}
}

جهت اطلاع؛ قابليت استفاده از «كد به عنوان اطلاعات» هم مفهوم جديدي نيست و براي مثال زبان Lisp چند دهه است كه آن‌را ارائه داده است!

براي مطالعه بيشتر: