یادداشتهای یک برنامه نویس

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

یادداشتهای یک برنامه نویس

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

۱ مطلب با کلمه‌ی کلیدی «var» ثبت شده است

دوشنبه, ۷ مهر ۱۳۹۳، ۱۱:۵۲ ق.ظ

کالبد شکافی یک ساختار Dynamic

 

معمولا همه کسانی که در ابتدا با تکنولوژیِ ASP .NET MVC آشنا می شوند در مواجهه با ساختارِ ViewData و ViewBag شگفت زده شده و حتی برخی به دلایلِ متعدد (دلایل مثلی پرهیز از بی نظمی) از استفاده از آن خودداری می کنند .

بعد از مراجعه به منابع و مراجع معتبر فنی آنچه در مورد ساختار ViewBag و دلیل انعطاف پذیری شگفت انگیزِ آن در می یابیم، این است که این ساختار اصطلاحا یک Dynamic Dictionary بوده که قادر است در موارد لازم انواع Property های مختلف را تقریبا بدون هیچ محدودیتی در Type و تعداد پذیرفته و چنانچه همه ما می دانیم در ASP .NET MVC از آن به عنوان یکی از راههای انتقال Data از Controller به View استفاده می شود .

به عنوان مثال، مواردِ زیر در MVC با اینکه کاملا عجیب به نظر می رسند اما کاملا صحیح می باشند :

ViewBag.ThisOne = 10;

ViewBag.AndThisToo = "Some Data";

از سویِ دیگر در مبحث Dynamic و در C# ما ابتدا در روایت سوم (C# 3.0) با Keyword یا کلمه کلیدی var و در روایت چهارم با Keyword جدید تر dynamic روبرو هستیم . اما از مهمترین سوالهایی که برای  همه مطرح می شود این است که اولا تفاوت این دو در چیست و ثانیا چگونه می توان یک ساختار dynamic با عملکردی مشابه با ViewBag ایجاد کرد؟

برای پاسخ به این سوالات به نظرم طراحی چند مثال ساده ضروریست که با استفاده از آنها بتوان برخی از جنبه های این موضوع را روشن نمود.

 

namespace ConsoleApplication

{

    class SimpleEmployee

    {

        public int Id { get; set; }

        public string FirstName { get; set; }   

    } 

 

    class Program

    {

 

        static void Main(string[] args)

        {           

            var employee = new SimpleEmployee() { Id = 1, FirstName = "Mehran"};

            dynamic employee2;

            employee2 = new SimpleEmployee() { Id = 1, FirstName = "Mehran"};

            Console.WriteLine("—-- Using var -----------");

            Console.WriteLine("Employee First Name:" + employee.FirstName);

            // Compile time Error

            //Console.WriteLine("Employee Last Name:" + employee.LastName);

            Console.WriteLine("—-- Using dynamic -----------");

            Console.WriteLine("Employee2 FirstName:" + employee2.FirstName);

            // Compile successfully but it will throw exception at runtime

            Console.WriteLine("Employee Last Name:" + employee2.LastName);

            Console.ReadLine();

        }

    }

}

 

در مثال قبل شواهدی وجود دارد که می توان بر اساس آنها پاسخ سوال اول را مشخص نمود. اولا متغیر داینامیک تعریف شده با var حتما در همان لحظه معرفی باید دارای مقدار باشد که در مورد dynamic چنانچه در مثال به روشنی مشخص شده این وضعیت اجباری نیست. ثانیا عدم وجود LastName در آبجکت employee که با var ایجاد شده در همان زمان کامپایل با مشکل روبرو میشود در حالیکه در آبجکت employee2 تا قبل از اجرا ، کامپایلر هیچ شکایتی از عدم وجود LastName نمی کند . مورد دیگری که باید به آن توجه کرد این است که آبجکت ایجاد شده با var از حمایت Intellisense برخوردار است و در آبجکت ایجاد شده با dynamic این وضعیت وجود ندارد .

اما با اینکه در هر دو حالت ما یک Dynamic Object ایجاد کرده ایم هنوز اساسا قادر نیستیم که درست نظیر ViewBag از آن استفاده کنیم . پس باید تغییراتی در کد ایجاد کنیم.

 

هیچ تردیدی نیست که منشاء این رفتار یا خاصیت شگفت انگیز آبجکت های Dynamic را که وولورین Object Oriented محسوب میشود باید در وراثت جستجو کرد. حتی با یک بررسی ساده در سلسله مراتب Ancestor ها و اجداد این جهش فوق العاده می توان دریافت که نقش IDynamicMetaObjectProvider چقدر حیاتی و مهم می باشد .

در واقع کلمه کلیدی dynamic باعث میشود که DLR یا همان Dynamic Library Runtime ماهیت بسیار متفاوتی برای Objectی که با dynamic معرفی شده ، تعیین کند و این همه توضیح صرفا در اصطلاح Late Binding خلاصه میشود .

پس آنچه مسلم است به منظور دستیابی به یک Dynamic Object تمام عیار ، باید توجه خود را به  IDynamicMetaObjectProvider معطوف نماییم . زیرا این اینترفیس و متدهای موجود در آن به یک کلاس این امکان را می دهند که رفتار Late Bind از خود نشان داده و DLR مراقب و کنترل کننده این رفتار خواهد بود .  خوشبختانه کلاسهای DynamicObject و ExpandoObject هر دو این Interface را Implement کرده اند و بنا براین بهتر است این دو مورد را با دقت بیشتری بررسی کنیم .  زیرا این دو کلید دستیابی به پاسخ سوال دوم ما هستند و به واسطه همین دو کلاس ما قادر خواهیم بود که چرخ را دوباره اختراع کرده و رفتاری مثل رفتار ViewBag را احیانا در کلاسهای مورد نیاز خود تقلید  کنیم .

قبل از گسترش مثال قبلی خود و در مقام مقایسه این دو کلاس باید اضافه کنم که ExpandoObject بر خلاف DynamicObject یک لقمه آماده بوده و برای رسیدن به هدفی که در نظر داریم شاید بهترین گزینه باشد . اما اگر احیانا در نظر داشته باشیم که بنا به دلایل متعدد، کنترل کامل این وضعیت را بر عهده داشته باشیم لازم است کلاسی وارث کلاس DynamicObject ایجاد کرده و با Override نمودن متدهای TryGetMember و TrySetMember نهایت انعطاف لازم را ایجاد کنیم
تقریبا مشابه این عملیات در یکی از مثالهای MSDN به سادگی نشان داده شده است و مثال ما از نظر هدفی که دنبال میکند دقیقا مشابه آن است . این مثال را در 
http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.trygetmember(v=vs.110).aspx
می توانید دنبال کنید .
در مثال بعد جزییات این کار و گسترش کلاسی که در ابتدا ایجاد کرده بودیم، نشان داده شده است :

 

    class SimpleEmployee : DynamicObject

    {

        private readonly Dictionary<string, dynamic> _properties = new Dictionary<string, dynamic>(StringComparer.InvariantCultureIgnoreCase);

 

        public override bool TryGetMember(GetMemberBinder binder, out dynamic result)

        {

            result = this._properties.ContainsKey(binder.Name) ? this._properties[binder.Name] : null;

 

            return true;

        }

 

        public override bool TrySetMember(SetMemberBinder binder, dynamic value)

        {

            if (value == null)

            {

                if (_properties.ContainsKey(binder.Name))

                    _properties.Remove(binder.Name);

            }

            else

                _properties[binder.Name] = value;

 

            return true;

        }

        public int Id { get; set; }

        public string FirstName { get; set; }

    }

 

چنانچه مشاهده میکنید گسترش کلاس ساده SimpleEmployee ما که اینک شاید سادگی صفت مناسبی برای توصیف آن نباشد در سه مرحله انجام شده است:

در قدم اول یک Data Member به نام _properties با دسترسی private از نوع Dictionary<string, dynamic> اضافه شده است .  این در حقیقت Container اصلی همه Property هاییست که قادر هستند به صورت Dynamic به کلاس ما اضافه شوند . فقط لازم است در اینجا به یک نکته مهم توجه کنیم که بواسطه استفاده از StringComparer.InvariantCultureIgnoreCase همه Property هایی که به صورت Dynamic اضافه میشوند بدون استثنا Case Insensitive هستند و هیچ حساسیتی در مطابقت حروف بزرگ و کوچک در آنها وجود ندارد . این موضوع در مثال بعدی با تاکید نشان داده شده است .

در اقدام بعدی متدهای TryGetMember و TrySetMember را در کلاس خود Override کرده و تقریبا مشابه با وضعیت معمول استفاده از متد های get و set از آنها استفاده میکنیم . نوع دسترسی public در آنها نباید این شبهه را بوجود بیاورد که استفاده از Property های Dynamic صرفا با فراخوانی این دو متد میسر می باشد . در واقع ما با استفاده از Syntax رایج و مشهور اپراتور دات (.) از Property های ایجاد شده استفاده خواهیم کرد و DLR فراخوانی این دو متد را به صورت ضمنی (Implicit) انجام خواهد داد:

 

    class Program

    {

 

        static void Main(string[] args)

        {

            dynamic employee;

            employee = new SimpleEmployee() { Id = 1, FirstName = "Mehran"};

            employee.LastName = "Hossein nia";

            employee.MyBadHabit = "Smoking";

            employee.Salary = 900000;

            employee.HireDate = DateTime.Now.AddYears(-15);

            employee.IJustRememberedNow = "I don't have boss";

            employee.EchoMessage = new Action<string>(s =>

            {

 

                Console.WriteLine(s);

 

            });

 

            Console.WriteLine("Employee FirstName:" + employee.FirstName);

            Console.WriteLine("Employee Last Name:" + employee.LastName);

            Console.WriteLine("Employee Last Name:" + employee.lAsTnAme);                       Console.WriteLine("Employee Bad Habit:" + employee.MyBadHabit);

            Console.WriteLine("Employee Salary:" + employee.Salary);

            Console.WriteLine("Employee Hire Date:" + employee.HireDate);

            Console.WriteLine("Employee IJustRememberedNow:" + employee.IJustRememberedNow);

            employee.EchoMessage("Simply Show this message at the end.");

 

            Console.ReadLine();

 

        }

    }

 

تقریبا به استثنای متد EchoMessge درباره همه اجزای این مثال در حد بضاعت توضیحاتی داده شده است . اما در حین نوشتن این مثال به نظرم رسید که  می توان با یک ترفند ساده این ویژگی قابلیت افزایش Dynamic اعضای کلاس را صرفا محدود به Property ها نکرده و با استفاده از متدهای Anonymous انعطاف این کلاس را افزایش داد. چنانچه در کد بوضوح قابل مشاهده است نتیجه این کار حتی فراتر از تصور خودم بود و در حال حاضر هیچ توضیحی برای انگیزه استفاده از آن ندارم .

همانطور که قبلا هم اشاره شد استفاده از ExpandoObject بسیار ساده و صرفا با یک نمونه سازی میسر است . در زیر نمونه ای از آن نشان داده شده است :


 

            dynamic contact = new ExpandoObject();

            contact.NewProperty = "I am new";

            contact.WhatEverYouWant = "Real Freedom";

 

۰ نظر موافقین ۰ مخالفین ۰ ۰۷ مهر ۹۳ ، ۱۱:۵۲
مهران حسین نیا