اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
چهار دقیقه
delegateها، نوعهایی هستند که ارجاعی را به یک متد دارند؛ بسیار شبیه به function pointers در C و CPP هستند، اما برخلاف آنها، delegates شیءگرا بوده، به امضای متد اهمیت داده و همچنین کد مدیریت شده و امن به شمار میروند.
سیر تکاملی delegates را در مثال ساده زیر میتوان ملاحظه کرد:
معنای کلمه delegate، واگذاری مسئولیت است. به این معنا که ما در متد UseDelegate، نمیدانیم addMethod به چه نحوی تعریف خواهد شد. فقط میدانیم که امضای آن چیست.
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی میشد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بینام، سادهتر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم سادهتر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نامهای Action و Func نیز ارائه گردیدهاند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شدهاند. تفاوتهای این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت میدهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
در دو مثال فوق، نحوه تعریف inline یک Action و یا Func را ملاحظه میکنید. Action به متدی اشاره میکند که خروجی ندارد و در اینجا تنها یک ورودی int را میپذیرد. Func در اینجا به تابعی اشاره میکند که یک ورودی int را دریافت کرده و یک خروجی string را باز میگرداند.
پس از این مقدمه، در ادامه قصد داریم مثالهای دنیای واقعی Action و Func را که در سالهای اخیر بسیار متداول شدهاند، بررسی کنیم.
مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانههای ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاسهای دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمیاندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار میکنند و کتابخانه ما نمیداند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده میکند:
در اینجا ابتدا باید این interface را در طی یک کلاس جدید (مثلا HelloSchedule) پیاده سازی کرد و سپس حاصل را در کلاس Runner استفاده نمود.
نظر شما در مورد این طراحی ساده شده چیست؟
با توجه به اینکه هدف از معرفی interface در طراحی اول، واگذاری مسئولیت نحوه تعریف متد Run به کلاسی دیگر است، به همین طراحی با استفاده از یک Action delegate نیز میتوان رسید. مهمترین مزیت آن، حجم بسیار کمتر کدنویسی استفاده کننده نهایی از API تعریف شده ما است. به علاوه امکان inline coding نیز فراهم گردیده است و در همان محل تعریف Action، بدنه آنرا نیز میتوان تعریف کرد.
بدیهی است delegates نمیتوانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهلهای از کلاسی است که یک interface را پیاده سازی کردهاست.
علت استفاده بیش از حد interfaceها در سایر زبانها برای ابتداییترین کارها، کمبود امکانات پایهای آن زبانها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همهجا از interfaceها استفاده کنند.
ادامه دارد ...
سیر تکاملی delegates را در مثال ساده زیر میتوان ملاحظه کرد:
using System; namespace ActionFuncSamples { public delegate int AddMethodDelegate(int a); public class DelegateSample { public void UseDelegate(AddMethodDelegate addMethod) { Console.WriteLine(addMethod(5)); } } public class Helper { public int CustomAdd(int a) { return ++a; } } class Program { static void Main(string[] args) { Helper helper = new Helper(); // .NET 1 AddMethodDelegate addMethod = new AddMethodDelegate(helper.CustomAdd); new DelegateSample().UseDelegate(addMethod); // .NET 2, anonymous delegates new DelegateSample().UseDelegate(delegate(int a) { return helper.CustomAdd(a); }); // .NET 3.5 new DelegateSample().UseDelegate(a => helper.CustomAdd(a)); } } }
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی میشد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بینام، سادهتر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم سادهتر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نامهای Action و Func نیز ارائه گردیدهاند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شدهاند. تفاوتهای این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت میدهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
Action<int> example1 = x => Console.WriteLine("Write {0}", x); example1(5); Func<int, string> example2 = x => string.Format("{0:n0}", x); Console.WriteLine(example2(5000));
پس از این مقدمه، در ادامه قصد داریم مثالهای دنیای واقعی Action و Func را که در سالهای اخیر بسیار متداول شدهاند، بررسی کنیم.
مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانههای ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاسهای دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمیاندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار میکنند و کتابخانه ما نمیداند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده میکند:
using System; namespace ActionFuncSamples { public interface ISchedule { void Run(); } public class Runner { public void Exceute(ISchedule schedule) { schedule.Run(); } } public class HelloSchedule : ISchedule { public void Run() { Console.WriteLine("Just Run!"); } } class Program { static void Main(string[] args) { new Runner().Exceute(new HelloSchedule()); } } }
نظر شما در مورد این طراحی ساده شده چیست؟
using System; namespace ActionFuncSamples { public class Schedule { public void Exceute(Action run) { run(); } } class Program { static void Main(string[] args) { new Schedule().Exceute(() => Console.WriteLine("Just Run!")); } } }
بدیهی است delegates نمیتوانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهلهای از کلاسی است که یک interface را پیاده سازی کردهاست.
علت استفاده بیش از حد interfaceها در سایر زبانها برای ابتداییترین کارها، کمبود امکانات پایهای آن زبانها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همهجا از interfaceها استفاده کنند.
ادامه دارد ...