رویدادها در #C — به زبان ساده

۱۹۰۴ بازدید
آخرین به‌روزرسانی: ۱۸ شهریور ۱۴۰۲
زمان مطالعه: ۷ دقیقه
رویدادها در #C — به زبان ساده

همه ما معنی لفظی واژه «رویداد» را می‌دانیم. رویداد چیزی است که قرار است اتفاق بیفتد و یا اتفاق افتاده است. معنی رویداد (Event) در زبان برنامه‌نویسی سی شارپ نیز جز این نیست. از لحاظ مفاهیم برنامه‌نویسی، رویداد به معنی شیئی است که یک رویداد تولید کرده است و شیء یا اشیای دیگر باید آن رویداد را مدیریت کنند. شیء (ناشر) که رویداد را تولید می‌کند در واقع به اشیای دیگر (مشترکان) اطلاع می‌دهد که چیزی رخ داده است و آن اشیا (مشترک) باید کاری در خصوص آن رویداد انجام دهند (مدیریت کنند). این رفتار بین اشیا به طور عمده در الگوی طراحی نرم‌افزار observer (مدل ناشر-مشترک) دیده می‌شود. ما در این مقاله تلاش می‌کنیم مفهوم رویدادها در #C را به زبانی ساده توضیح دهیم.

997696

Delegate

از آنجا که رویدادها (Events) و وکالت‌ها (Delegates) به طور جدایی‌ناپذیری با یکدیگر پیوسته‌ هستند، لازم است که ابتدا با طرز کار وکالت‌ها آشنا شویم. منظور از delegate در زبان سی شارپ یک نوع شیء است. این نوع از شیء می‌تواند به عنوان یک متغیر مورد استفاده قرار گیرد. اما یک متغیر delegate به یک مقدار اشاره نمی‌کند، بلکه به یک متد اشاره دارد. از این رو شیء delegate تنها می‌تواند به متدهایی اشاره کند که امضای آن با delegate تطبیق پیدا کند.

ساختار اعلان یک delegate به صورت زیر است:

access_specifier delegate retun_type delegateName(parameter_list);

به مثال زیر توجه کنید:

public delegate int CalculateDelegate(int a, int b);

متدهای زیر با delegate به نام CalculateDelegate تطبیق می‌یابند:

public int Sum(int a, int b) { return a + b; }
public int Subtract(int a, int b) { return a - b; }
public int Average(int a, int b) { return (a + b) / 2; }
public int Multiply(int a, int b) { return a * b; }
public int Divide(int a, int b) { return a / b; }

رویدادها در #C

 

ما با استفاده از multicasting می‌توانیم این متدها را با هم ترکیب کرده و آن‌ها را در یک شیء CalculateDelegate نگهداریم. سپس با استفاده از این شیء می‌توانیم همه متدها را اجرا کنیم.

1var a = 25;
2var b = 5;
34CalculateDelegate calc = Sum; //or var calc = new CalculateDelegate(Sum);
5calc += Subtract;
6calc += Average;
7calc += Multiply;
8calc += Divide;
910//By using -=, delegate can remove a method’s reference from the delegate’s instance.
11//For example: calc -= Multiply; //the method Multiply is removed from the invocation list of calc.

برای اجرای (اجرا یا فراخوانی) همه متدها با استفاده از شیء calc می‌توانید از InvocationList مربوط به delegate استفاده کنید.

1foreach (CalculateDelegate cd in calc.GetInvocationList())
2{
3    Console.WriteLine(cd(a, b));
4}

واریانس در Delegate

زمانی که متدی را به delegate انتساب می‌دهیم، امضای متد لزومی ندارد دقیقاً با delegate تطبیق پیدا کند. این مسئله کوواریانس و کنتراواریانس نامیده می‌شد. کوواریانس روی نوع بازگشتی متد اعمال می‌شود، در حالی که کنتراواریانس روی نوع پارامتر متد اعمال خواهد شد. در ادامه مثالی از کوواریانس را می‌بینید:

1class Parent { }
23class Child : Parent { }
45delegate Parent CovarianceDelegate(); // the delegate's return type is the base class
67static Child CovarianceMethod() // the method's return type is the derived class
8{
9    return new Child();
10}
1112static void Main(string[] args)
13{
14    CovarianceDelegate delegateObject = CovarianceMethod;
15    Parent result = delegateObject();
16    
17    /*
18        cast is needed if you want to hold the return value in an instance of the derived class e.g.:
19        Child result = (Child)del(); or Child result = del() as Child;
20    */
21}

در ادامه مثالی از یک کنتراواریانس را ملاحظه می‌کنید:

1class Parent { }
23class Child : Parent { }
45delegate void ContravarianceDelegate(Child c); // the delegate's parameter type is of the derived class
67static void Method(Parent p) // the method's parameter type is of the base class
8{
9    var childObject = p as Child;
10}
1112static void Main(string[] args)
13{
14    ContravarianceDelegate delegateObject = Method;
15    Child child = new Child();
16    delegateObject(child);
17}

Delegate‌-های داخلی

#C برخی delegate-های داخلی را عرضه کرده است که برای مقاصد رایج مختلف، مفید هستند. این delegate-ها یک نمادگذاری اختصاری ارائه می‌کنند که تقریباً نیاز به اعلان کردن انواع delegate را رفع می‌کند. فهرست آن‌ها به شرح زیر است:

  • Action - این نماد به همراه متدهایی استفاده می‌شود که مقداری بازگشت نمی‌دهند و هیچ فهرست پارامتری ندارند.
  • <>Action - به همراه متدهایی استفاده می‌شود که دست کم یک آرگومان دارند و مقداری بازگشت نمی‌دهند.
  • <>Func - به همراه متدهایی استفاده می‌شود که مقداری بازگشت می‌دهند و دارای فهرست پارامتر هستند.
  • <>Predicate - این نماد نشان‌دهنده متدی است که یک پارامتر ورودی می‌گیرد و یک مقدار بولی بر مبنای برخی معیارها بازگشت می‌دهد.

این مقدار توضیحات در خصوص delegate-ها کافی است. مطالب آنلاین زیادی وجود دارند که می‌توانید با بهره‌گیری از آن‌ها با delegate-ها آشنا شوید. اما تمرکز این مقاله روی توضیح استفاده از delegate-ها در یک رویداد است.

مدیریت و تولید یک رویداد در C#‎

زمانی که یک delegate اعلان شد، هرکس می‌تواند ارجاع متد آن را با استفاده از نماد تساوی (=) بازنویسی کند. فرض کنید یک شیء delegate دارید که چند ارجاع متد در خود جای داده است. اگر کسی از = برای اشاره به یک متد جدید استفاده کند در این صورت همه ارجاع‌های دیگر متد نیز از دست خواهند رفت. از سوی دیگر Event اقدام به کپسوله‌سازی یک delegate می‌کند و بدین ترتیب جلوی بازنویسی یک ارجاع متد از طریق محدودسازی استفاده از عملگر انتساب (=) گرفته می‌شود.

مشکل دیگر این است که یک delegate می‌تواند خارج از یک کلاس فراخوانی (در واقع از هر جایی) فراخوانی شود. Event این مشکل را نیز حل می‌کند، زیرا رویداد نمی‌تواند خارج از کلاس فراخوانی شود. بدین ترتیب مطمئن می‌شویم که رویداد تنها از سوی کدی فراخوانی می‌شود که بخشی از کلاس (ناشر) است و رویدادی را که تعریف کرده در صورت برقرار شدن شرایط خاص اجرا می‌کند. همچنین یک رویداد همواره یک عضو داده‌ای از یک کلاس یا struct است و می‌تواند درون یک متد اعلان شود.

فرض کنید می‌خواهیم زمانی که فرد روی دکمه‌ای روی گوشی می‌زند یا زمانی که دما از سطح معینی فراتر می‌رود، اتفاقات زیر بیفتند:

  • تهویه هوا روشن شود.
  • درها‌/پنجره‌ها بسته شوند.
  • یک بستنی ارزان شکلاتی-نعنایی سفارش داده شود.

این سه کار که باید اتفاق بیفتند، اکشن نام دارند و از سوی یک شیء delegate در زمان رخ دادن رویداد وکالت می‌شوند. بنابراین دو رویداد مختلف وجود دارند که یک delegate را برای اجرای برخی متدها تریگر می‌کنند.

رویدادها در #C

اکنون که دو تریگر رویداد یعنی موبایل و دماسنج داریم، می‌توانیم از یک اینترفیس استفاده کنیم، زیرا هر دوی آن‌ها کد یکسانی دارند. بنابراین کار خود را با اینترفیس اول آغاز می‌کنیم:

1public interface IEventTrigger
2{
3    event EventHandler<TemperatureEventArgs> OnTemperatureChanged;
45    int Temperature { set; }
6}

توجه کنید که ما از delegate ژنریک NET. به نام EventHandler<>‎ استفاده می‌کنیم و آرگومان‌های سفارشی خاص خود به نام EventArgs را ارسال می‌نماییم. کلاس EventHandler<>‎ سفارشی به صورت زیر است:

1public class TemperatureEventArgs : EventArgs
2{
3    public int Value { get; set; }
4}

TemperatureEventArgs از کلاس ‎.NET EventArgs ارث‌بری کرده است و تنها یک بخش از اطلاعات یعنی مقدار دما را نگهداری می‌کند در ادامه کلاس مشترک را نیز ایجاد می‌کنیم که رویداد ‎.NET EventArgs را پیاده‌سازی خواهد کرد:

1public class Thermometer : IEventTrigger
2{
3    private int maxTemperature;
45    public Thermometer(int maxTemperature)
6    {
7        this.maxTemperature = maxTemperature;
8    }
910    /*
11            Initialize the event to an empty delegate
12            so that you don't need the null check around raising the event
13            because the event will never be null.
14            Only members in this class can set the event to null, outsiders can't.
15     */
16    public event EventHandler<TemperatureEventArgs> OnTemperatureChanged = delegate{};
17  
18    public int Temperature
19    {
20        set
21        {
22            var temperature = value;
2324            if(temperature > maxTemperature)
25            {
26                var temperatureValue = new TemperatureEventArgs
27                {
28                    Value = temperature,
29                };
3031                OnTemperatureChanged(this, temperatureValue);
32            }
33        }
34    }
35}

کلاس mobile یک تریگر دیگر است که کد یکسانی با کلاس thermometer دارد. کلاس thermometer و کلاس Mobile دارای یک فیلد خصوصی هستند که مقدار بالاترین دما را نگه‌داری می‌کند و از طریق سازنده تعیین می‌شود. هر زمان که دما از مقدار بالاترین دما فراتر رود، کلاس thermometer اقدام به فراخوانی delegate می‌کند:

OnTemperatureChanged(this, temperatureValue);

در این زمان delegate به نام OnTemperatureChanged مسئولیت اجرای اکشن‌های subscriber را بر عهده دارد. شاید بپرسید که مشترک از کجا می‌داند متدها اجرا خواهند شد؟ ناشر نه تنها به مشترک اطلاع می‌دهد، بلکه اجرای متدهای مشترک را نیز از طریق یک delegate به نام OnTemperatureChanged تریگر می‌کند.

کلیدواژه this به کنترل/تریگری اشاره دارد که delegate را اجرا کرده است و در این مورد thermometer است. پارامتر temperatureValue مقدار دمای کنونی است و از طریق آرگومان رویداد به مشترک یعنی اپلیکیشن یا دستگاهی که قرار است اکشن‌ها یا متدهای نگهداری شده از سوی delegate را اجرا کند، ارسال می‌شود. مشترکی که به تریگرها گوش می‌دهد تا برخی اکشن‌ها را اجرا کند به صورت زیر است:

1static void Main(string[] args)
2{
3    //IEventTrigger trigger = new Mobile(30);
4    IEventTrigger trigger = new Thermometer(30);
56    trigger.OnTemperatureChanged += (o, e) => Console.WriteLine($"Temperature is changed to {e.Value} °C. {((o is Mobile) ? "Triggered manually by mobile." : "Triggered automatically by the thermometer.")}");
7    trigger.OnTemperatureChanged += (o, e) => Console.WriteLine("1. Ordering mint chocolate chip ice-cream!");
8    trigger.OnTemperatureChanged += (o, e) => Console.WriteLine("2. Turning on the A/C.");
9    trigger.OnTemperatureChanged += (o, e) => Console.WriteLine("3. Closing all doors & windows.");
1011    trigger.Temperature = 32; //when the temperature is below 30 then none of the actions will be performed.
12}

تریگر (موبایل/دماسنج) ایجاد و بالاترین مقدار دما به صورت زیر تعیین شده است:

IEventTrigger trigger = new Thermometer(30);

برخی اکشن‌ها به یک delegate با نام OnTemperatureChanged اضافه شده‌اند. زمانی که دما (خارج از ناشر و مشترک) تغییر یابد، set accessor در تریگر اجرا می‌شود. متد set accessor بررسی می‌کند آیا دمای کنونی بالاتر از سطح بیشینه دما رسیده است یا نه. اگر چنین باشد، یک delegate با نام OnTemperatureChanged در شیء ناشر اجرا می‌شود. این delegate نماینده delegate روی شیء تریگر یعنی trigger.OnTemperatureChanged است. بنابراین زمانی که delegate در متد set accessor اجرا شود، همه متدهای ارجاع‌یافته/الصاق‌یافته فراخوانی خواهند شد.

رویدادها در #C

سخن پایانی

در این مقاله تنها از یک مشترک استفاده شده است، اما شما می‌توانید چندین مشترک داشته باشید که در یک یا چند اشتراک ثبت نام کنند. اشتراک‌ها از سوی ناشر منتشر می‌شوند. مشترک از += برای ثبت نام در رویداد (های) یک ناشر استفاده می‌کند. این عملگر به ناشر اعلام/درخواست می‌کند که «این اکشن‌ها را در زمانی که شرط برقرار شد اجرا کن». شرط از سوی ناشر تعریف می‌شود. اکشن‌ها از سوی مشترکان تعریف می‌شوند و هر مشترک می‌تواند اکشن‌های متفاوتی برای اجرا در زمان وقوع یک رویداد داشته باشد. امیدواریم مطالعه این مقاله برای شما مفید بوده باشد و توانسته باشید در خصوص رویدادها و delegate-ها در سی شارپ اطلاعاتی کسب کنید.

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
dinesh.jethoe
۶ دیدگاه برای «رویدادها در #C — به زبان ساده»

با سلام، قسمت اسکرول بار قسمت برچسب ها، مشکل فنی داره.

با سلام؛

از بازخورد شما بسیار سپاس‌گزاریم. اگر نوع مرورگر، سیستم عامل و دستگاه مورد استفاده خودتون رو به همراه مشکل اطلاع بدین، این مشکل را ساده‌تر بررسی و برطرف می‌کنیم.

با تشکر از همراهی شما با مجله فرادرس

با سلام.
در قطعه کد:
access_specifier delegate retun_type delegateName(parameter_list)
فکر میکنم سمی کالن لازم باشه.
با تشکر از زحمات زیادتون، لطفاً مثال های عملی از دلیگیت ها با موضوع رسم اشکال هندسی و عملیات محاسبات ریاضی بزنید.
چرا که بنده این مثال های پیچیده، برایم قابل فهم نیست و فقط مثال از اشکال هندسی را می فهمم.
باز هم ممنون


با سلام و احترام؛

صمیمانه از همراهی شما با مجله فرادرس و ارائه بازخورد سپاس‌گزاریم.

این مورد اصلاح شد.

حتماً نهایت سعی خود را خواهیم داشت تا در صورت امکان در آینده مطلب دیگری را با این موضوع در مجله فرادرس منتشر کنیم و در آن مثال‌های ساده‌تری را ارائه دهیم. البته بخش هفتم دوره آموزشی زیر به موضوع Delegate اختصاص دارد که در صورت تمایل می‌توانید فیلم آموزشی مربوط به این بخش را مشاهده کنید.

  • آموزش شی گرایی در سی شارپ C#‎

  • برای شما آرزوی سلامتی و موفقیت داریم.

    سلام
    در قسمت Delegate جمله “اما یک متغیر delegate به یک مقدار اشاره می‌کند، بلکه به یک متد اشاره دارد.” چک شود
    با تشکر

    سلام و وقت بخیر؛
    مورد مربوطه اصلاح شد.
    از توجه و همراهی شما با مجله فرادرس متشکریم.

    نظر شما چیست؟

    نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *