۱۳۹۰/۰۵/۲۰

كاربردهاي Static reflection - قسمت اول


در مورد static reflection مقدمه‌اي پيشتر در اين سايت قابل مطالعه است (^) و پيشنياز بحث جاري است. در ادامه قصد داريم يك سري از كاربردهاي متداول آن‌را كه اين روزها در گوشه و كنار وب يافت مي‌شود، به زبان ساده بررسي كنيم.

بهبود كدهاي موجود

از static reflection در دو حالت كلي مي‌توان استفاده كرد. يا قرار است كتابخانه‌اي را از صفر طراحي كنيم يا اينكه خير؛ كتابخانه‌اي موجود است و مي‌خواهيم كيفيت آن‌را بهبود ببخشيم. هدف اصلي هم «حذف رشته‌ها» و «استفاده از كد بجاي رشته‌ها» است.
براي مثال قطعه كد زير يك مثال متداول مرتبط با WPF و يا Silverlight است. در آن با پياده سازي اينترفيس INotifyPropertyChanged و استفاده از متد raisePropertyChanged ، به رابط كاربري برنامه اعلام خواهيم كرد كه لطفا خودت را بر اساس اطلاعات جديد تنظيم شده در قسمت set خاصيت Name ، به روز كن:
using System.ComponentModel;

namespace StaticReflection
{
    public class User : INotifyPropertyChanged
    {
        string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                raisePropertyChanged("Name");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        void raisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler == null) return;
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

تعاريف قسمت PropertyChangedEventArgs اين پياده سازي، خارج از كنترل ما است و در دات نت فريم ورك تعريف شده است. حتما هم نياز به رشته دارد؛ آن هم نام خاصيتي كه تغيير كرده است. چقدر خوب مي‌شد اگر مي‌توانستيم اين رشته را حذف كنيم تا كامپايلر بتواند صحت بكارگيري اطلاعات وارد شده را دقيقا پيش از اجراي برنامه بررسي كند. الان فقط در زمان اجرا است كه متوجه خواهيم شد، مثلا آيا به روز رساني مورد نظر صورت گرفته‌است يا خير؛ اگر نه، يعني احتمالا يك اشتباه تايپي جايي وجود دارد.
براي بهبود اين كد همانطور كه در قسمت قبل نيز گفته شد، از تركيب كلاس‌هاي Expression و Func استفاده خواهيم كرد. در اينجا Func قرار نيست چيزي را اجرا كند، بلكه از آن به عنوان قطعه‌ كدي كه اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده مي‌شود. اين استخراج اطلاعات هم توسط كلاس Expression انجام مي‌شود. بنابراين قسمت اول بهبود كد به صورت زير شروع مي‌شود:
void raisePropertyChanged(Expression<Func<object>> expression)

الان اگر متد raisePropertyChanged بكارگرفته شده در خاصيت Name را بخواهيم اصلاح كنيم، حداقل با دو واقعه‌ي مطلوب زير مواجه خواهيم شد:
Intellisense به صورت خودكار كار مي‌كند:


حتي بدوي‌ترين ابزارهاي Refactoring موجود (منظور همان ابزار توكار VS.NET است!) هم امكان Refactoring را در اينجا فراهم خواهند ساخت:



در پايان كد تكميل شده فوق به شرح زير خواهد بود كه در آن از كلاس Expression جهت استخراج Member.Name استفاده شده است:
using System;
using System.ComponentModel;
using System.Linq.Expressions;

namespace StaticReflection
{
    public class User : INotifyPropertyChanged
    {
        string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                raisePropertyChanged(() => Name);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        void raisePropertyChanged(Expression<Func<object>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
                throw new InvalidOperationException("Not a member access.");

            var handler = PropertyChanged;
            if (handler == null) return;
            handler(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
        }
    }
}

در اينجا باز هم نهايتا به همان PropertyChangedEventArgs استاندارد و موجود، برمي‌گرديم؛ اما آرگومان رشته‌اي آن‌را به كمك تركيب كلاس‌هاي Expression و Func تامين خواهيم كرد.