سیر تکاملی 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ها استفاده کنند.
ادامه دارد ...