سؤال

ما هي الاختلافات بين المندوبين والأحداث؟ألا يحمل كلاهما إشارات إلى الوظائف التي يمكن تنفيذها؟

هل كانت مفيدة؟

المحلول

ان حدث يضيف الإعلان طبقة من التجريد والحماية على مندوب مثال.تمنع هذه الحماية عملاء المفوض من إعادة تعيين المفوض وقائمة الاستدعاء الخاصة به، وتسمح فقط بإضافة أو إزالة الأهداف من قائمة الاستدعاء.

نصائح أخرى

بالإضافة إلى الخصائص النحوية والتشغيلية، هناك أيضًا اختلاف دلالي.

المندوبون هم، من الناحية النظرية، قوالب وظيفية؛أي أنها تعبر عن عقد يجب أن تلتزم به الوظيفة حتى تعتبر من "نوع" المفوض.

تمثل الأحداث...حسنا، الأحداث.الغرض منها هو تنبيه شخص ما عند حدوث شيء ما، ونعم، يلتزمون بتعريف المندوب لكنهم ليسوا نفس الشيء.

حتى لو كانا نفس الشيء تمامًا (من الناحية النحوية وفي كود IL) فسيظل هناك اختلاف دلالي.بشكل عام أفضل أن يكون لدي اسمين مختلفين لمفهومين مختلفين، حتى لو تم تنفيذهما بنفس الطريقة (وهذا لا يعني أنني أحب الحصول على نفس الكود مرتين).

لفهم الاختلافات، يمكنك إلقاء نظرة على هذين المثالين

مثال مع المفوضين (في هذه الحالة، الإجراء - وهو نوع من المفوضين الذي لا يُرجع قيمة)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

لاستخدام المفوض، يجب عليك القيام بشيء مثل هذا:

Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

يعمل هذا الرمز بشكل جيد ولكن قد تكون لديك بعض نقاط الضعف.

على سبيل المثال، إذا كتبت هذا:

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

باستخدام السطر الأخير من التعليمات البرمجية، قمت بتجاوز السلوكيات السابقة مع فقدان سلوك واحد فقط + (لقد استخدمت = بدلاً من +=)

نقطة ضعف أخرى هي أن كل فئة تستخدم ملفك Animal يمكن رفع الطبقة RaiseEvent مجرد الاتصال به animal.RaiseEvent().

لتجنب هذه النقاط الضعيفة يمكنك استخدامها events شركة#.

سوف تتغير فئة الحيوان الخاصة بك بهذه الطريقة:

public class ArgsSpecial : EventArgs
{
    public ArgsSpecial (string val)
    {
        Operation=val;
    }

    public string Operation {get; set;}
} 

public class Animal
{
    // Empty delegate. In this way you are sure that value is always != null 
    // because no one outside of the class can change it.
    public event EventHandler<ArgsSpecial> Run = delegate{} 

    public void RaiseEvent()
    {  
         Run(this, new ArgsSpecial("Run faster"));
    }
}

لاستدعاء الأحداث

 Animal animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

اختلافات:

  1. أنت لا تستخدم ملكية عامة بل حقلًا عامًا (باستخدام الأحداث، يحمي المترجم حقولك من الوصول غير المرغوب فيه)
  2. لا يمكن تعيين الأحداث مباشرة.في هذه الحالة، لن يؤدي ذلك إلى ظهور الخطأ السابق الذي أظهرته عند تجاوز السلوك.
  3. لا يمكن لأي شخص خارج فصلك رفع الحدث.
  4. يمكن تضمين الأحداث في إعلان الواجهة، بينما لا يمكن تضمين الحقل

ملحوظات:

تم تعريف EventHandler باعتباره المفوض التالي:

public delegate void EventHandler (object sender, EventArgs e)

يستغرق المرسل (من نوع الكائن) ووسائط الحدث.يكون المرسل خاليًا إذا كان يأتي من طرق ثابتة.

هذا المثال الذي يستخدم EventHandler<ArgsSpecial>, ، ويمكن أيضًا كتابتها باستخدام EventHandler بدلاً من.

يشير إلى هنا للحصول على وثائق حول EventHandler

هنا رابط جيد آخر للإشارة إليه.http://csharpin Deep.com/Articles/Chapter2/Events.aspx

باختصار، المستفادة من المقالة - الأحداث عبارة عن تغليف على المندوبين.

إقتباس من المقال :

لنفترض أن الأحداث غير موجودة كمفهوم في C#/.NET.كيف يمكن لفئة أخرى الاشتراك في حدث ما؟ثلاثة خيارات:

  1. متغير مندوب عام

  2. متغير مندوب مدعوم بخاصية

  3. متغير مفوض باستخدام أساليب AddXXXHandler وRemoveXXXHandler

من الواضح أن الخيار الأول فظيع، لجميع الأسباب الطبيعية التي تجعلنا نكره المتغيرات العامة.

الخيار 2 أفضل قليلاً، ولكنه يسمح للمشتركين بتجاوز بعضهم البعض بشكل فعال - سيكون من السهل جدًا كتابة someInstance.MyEvent = eventsHandler;والتي من شأنها أن تحل محل أي معالجات أحداث موجودة بدلاً من إضافة معالج جديد.وبالإضافة إلى ذلك، لا تزال بحاجة إلى كتابة الخصائص.

الخيار 3 هو في الأساس ما توفره لك الأحداث، ولكن مع اتفاقية مضمونة (تم إنشاؤها بواسطة المترجم ومدعومة بعلامات إضافية في IL) وتنفيذ "مجاني" إذا كنت راضيًا عن الدلالات التي توفرها لك الأحداث المشابهة للحقل.يتم تغليف الاشتراك في الأحداث وإلغاء الاشتراك فيها دون السماح بالوصول التعسفي إلى قائمة معالجات الأحداث، ويمكن للغات أن تجعل الأمور أكثر بساطة من خلال توفير بناء الجملة لكل من الإعلان والاشتراك.

ملحوظة:إذا كان لديك حق الوصول إلى إطلاق العنان لـ C#5.0, ، اقرأ "القيود المفروضة على الاستخدام العادي للمندوبين" في الفصل 18 بعنوان "الأحداث" لفهم الاختلافات بين الاثنين بشكل أفضل.


يساعدني دائمًا الحصول على مثال بسيط وملموس.إذن هذه واحدة للمجتمع.أولاً أوضح كيف يمكنك استخدام المفوضين وحدهم للقيام بما تفعله الأحداث بالنسبة لنا.ثم أوضح كيف سيعمل نفس الحل مع مثيل EventHandler.ثم أشرح لماذا لا نريد أن نفعل ما أشرحه في المثال الأول.هذه المشاركة مستوحاة من مقالة بواسطة جون السكيت.

مثال 1:باستخدام مندوب عام

لنفترض أن لدي تطبيق WinForms مع مربع منسدل واحد.القائمة المنسدلة مرتبطة بـ List<Person>.حيث يمتلك الشخص خصائص المعرف والاسم والاسم المستعار ولون الشعر.يوجد في النموذج الرئيسي عنصر تحكم مستخدم مخصص يُظهر خصائص ذلك الشخص.عندما يقوم شخص ما بتحديد شخص ما في القائمة المنسدلة، يتم تحديث التسميات في عنصر تحكم المستخدم لإظهار خصائص الشخص المحدد.

enter image description here

هنا كيف يعمل ذلك.لدينا ثلاثة ملفات تساعدنا في تجميع هذا معًا:

  • Mediator.cs - فئة ثابتة تحمل المندوبين
  • Form1.cs - النموذج الرئيسي
  • DetailView.cs - يظهر تحكم المستخدم كافة التفاصيل

إليك الكود المناسب لكل فئة:

class Mediator
{
    public delegate void PersonChangedDelegate(Person p); //delegate type definition
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
    {
        if (PersonChangedDel != null)
        {
            PersonChangedDel(p);
        }
    }
}

هنا هو التحكم المستخدم لدينا:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.PersonChangedDel += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(Person p)
    {
        BindData(p);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

وأخيراً لدينا الكود التالي في Form1.cs الخاص بنا.نحن هنا نتصل بـ OnPersonChanged، الذي يستدعي أي رمز مشترك للمفوض.

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

نعم.هذه هي الطريقة التي ستنجح بها دون استخدام الأحداث و مجرد استخدام المندوبين.لقد قمنا فقط بوضع مندوب عام في الفصل الدراسي - يمكنك جعله ثابتًا أو فرديًا، أو أيًا كان.عظيم.

ولكن، ولكن، لا نريد أن نفعل ما وصفته للتو أعلاه.لأن الحقول العامة سيئة لأسباب عديدة.فما هي خياراتنا؟وكما يصف جون سكيت، إليك خياراتنا:

  1. متغير المفوض العام (وهذا ما فعلناه للتو أعلاه.لا تفعل هذا.لقد أخبرتك للتو أعلاه لماذا هو سيء)
  2. ضع المفوض في خاصية باستخدام get/set (المشكلة هنا هي أن المشتركين يمكنهم تجاوز بعضهم البعض - حتى نتمكن من الاشتراك في مجموعة من الأساليب للمفوض ومن ثم يمكننا أن نقول بطريق الخطأ PersonChangedDel = null, ، ومسح كافة الاشتراكات الأخرى.المشكلة الأخرى المتبقية هنا هي أنه بما أن المستخدمين لديهم حق الوصول إلى المفوض، فيمكنهم استدعاء الأهداف في قائمة الاستدعاء - لا نريد أن يكون لدى المستخدمين الخارجيين حق الوصول إلى وقت رفع أحداثنا.
  3. متغير مفوض باستخدام أساليب AddXXXHandler وRemoveXXXHandler

هذا الخيار الثالث هو في الأساس ما يقدمه لنا الحدث.عندما نعلن عن EventHandler، فإنه يتيح لنا الوصول إلى المفوض - ليس بشكل عام، وليس كخاصية، ولكن كهذا الشيء الذي نسميه حدثًا يحتوي فقط على إضافة/إزالة الوصول.

دعونا نرى كيف يبدو نفس البرنامج، ولكن الآن باستخدام حدث بدلاً من المفوض العام (لقد قمت أيضًا بتغيير الوسيط الخاص بنا إلى برنامج مفرد):

مثال 2:باستخدام EventHandler بدلاً من المفوض العام

الوسيط:

class Mediator
{

    private static readonly Mediator _Instance = new Mediator();

    private Mediator() { }

    public static Mediator GetInstance()
    {
        return _Instance;
    }

    public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.

    public void OnPersonChanged(object sender, Person p)
    {
        var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
        if (personChangedDelegate != null)
        {
            personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
        }
    }
}

لاحظ أنه إذا قمت بالضغط على F12 على EventHandler، فسوف يُظهر لك التعريف مجرد مفوض عام مع كائن "المرسل" الإضافي:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

التحكم بالمستخدم:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
    {
        BindData(e.Person);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

وأخيرًا، إليك رمز Form1.cs:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
        Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}

نظرًا لأن EventHandler يريد EventArgs كمعلمة، فقد قمت بإنشاء هذه الفئة بخاصية واحدة فقط:

class PersonChangedEventArgs
{
    public Person Person { get; set; }
}

ونأمل أن يوضح لكم هذا قليلًا سبب عقد الأحداث وكيف أنها مختلفة - ولكنها متماثلة من الناحية الوظيفية - مثل المندوبين.

يمكنك أيضًا استخدام الأحداث في إعلانات الواجهة، وليس ذلك بالنسبة للمفوضين.

يا له من سوء تفاهم كبير بين الفعاليات والمندوبين !!!يحدد المندوب نوع (مثل class, ، أو interface يفعل)، في حين أن الحدث هو مجرد نوع من الأعضاء (مثل الحقول والخصائص وما إلى ذلك).ومثل أي نوع آخر من الأعضاء، فإن الحدث له أيضًا نوع.ومع ذلك، في حالة وقوع حدث ما، يجب تحديد نوع الحدث بواسطة المندوب.على سبيل المثال، لا يمكنك الإعلان عن حدث من النوع المحدد بواسطة واجهة.

وفي الختام، يمكننا أن نجعل ما يلي ملاحظة:يجب أن يتم تحديد نوع الحدث من قبل المندوب.هذه هي العلاقة الرئيسية بين الحدث والمفوض ويتم وصفها في القسم II.18 تعريف الأحداث ل ECMA-335 (CLI) الأقسام من الأول إلى السادس:

في الاستخدام النموذجي، TypeSpec (إن وجد) يحدد مندوبا الذي يتطابق توقيعه مع الوسائط التي تم تمريرها إلى طريقة إطلاق الحدث.

لكن، هذه الحقيقة لا تعني أن الحدث يستخدم حقل مندوب دعم.في الحقيقة، قد يستخدم الحدث حقل دعم لأي نوع مختلف من بنية البيانات من اختيارك.إذا قمت بتنفيذ حدث بشكل صريح في C#، فلديك الحرية في اختيار طريقة تخزين الحدث معالجات الأحداث (لاحظ أن معالجات الأحداث هي أمثلة على نوع الحدث, ، والذي بدوره إلزامي أ نوع المندوب---من السابق ملاحظة).ولكن، يمكنك تخزين معالجات الأحداث هذه (وهي مثيلات مفوضة) في بنية بيانات مثل List أو أ Dictionary أو أي شيء آخر، أو حتى في مجال مندوب الدعم.لكن لا تنس أنه ليس من الضروري استخدام حقل المفوض.

الحدث في .net عبارة عن مجموعة محددة من أسلوب الإضافة وأسلوب الإزالة، وكلاهما يتوقع نوعًا معينًا من المفوضين.يمكن لكل من C# وvb.net إنشاء تعليمات برمجية تلقائيًا لطرق الإضافة والإزالة التي ستحدد المفوض للاحتفاظ باشتراكات الأحداث، وإضافة/إزالة المفوض الذي تم تمريره إلى/من مفوض الاشتراك هذا.سيقوم VB.net أيضًا بإنشاء تعليمات برمجية تلقائيًا (باستخدام عبارة RaiseEvent) لاستدعاء قائمة الاشتراكات فقط إذا كانت غير فارغة؛لسبب ما، لا يقوم C# بإنشاء الأخير.

لاحظ أنه على الرغم من أنه من الشائع إدارة اشتراكات الأحداث باستخدام مفوض البث المتعدد، إلا أن هذه ليست الوسيلة الوحيدة للقيام بذلك.من منظور عام، يحتاج المشترك المحتمل في الحدث إلى معرفة كيفية السماح للكائن بمعرفة أنه يريد تلقي الأحداث، لكنه لا يحتاج إلى معرفة الآلية التي سيستخدمها الناشر لرفع الأحداث.لاحظ أيضًا أنه على الرغم من أن من قام بتعريف بنية بيانات الحدث في .net كان يعتقد على ما يبدو أنه يجب أن تكون هناك وسيلة عامة لرفعها، إلا أنه لا يستخدم C# ولا vb.net هذه الميزة.

لتعريف الحدث بطريقة بسيطة:

الحدث هو مرجع إلى مندوب مع قيدين

  1. لا يمكن استدعاؤها مباشرة
  2. لا يمكن تعيين القيم مباشرة (على سبيل المثال، eventsObj = DelegateMethod)

ما ورد أعلاه هو نقاط الضعف بالنسبة للمندوبين ويتم معالجتها في الحدث.نموذج التعليمات البرمجية الكامل لإظهار الفرق في عازف الكمان موجود هنا https://dotnetfiddle.net/5iR3fB .

قم بتبديل التعليق بين الحدث والتفويض ورمز العميل الذي يستدعي/يعين قيمًا للتفويض لفهم الفرق

هنا هو الكود المضمن.

 /*
This is working program in Visual Studio.  It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
        Event is an delegate reference with two restrictions for increased protection

            1. Cannot be invoked directly
            2. Cannot assign value to delegate reference directly

Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/

public class RoomTemperatureController
{
    private int _roomTemperature = 25;//Default/Starting room Temperature
    private bool _isAirConditionTurnedOn = false;//Default AC is Off
    private bool _isHeatTurnedOn = false;//Default Heat is Off
    private bool _tempSimulator = false;
    public  delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
    // public  OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
    public  event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 

    public RoomTemperatureController()
    {
        WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
    }
    private void InternalRoomTemperatuerHandler(int roomTemp)
    {
        System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
    }

    //User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
    public bool TurnRoomTeperatureSimulator
    {
        set
        {
            _tempSimulator = value;
            if (value)
            {
                SimulateRoomTemperature(); //Turn on Simulator              
            }
        }
        get { return _tempSimulator; }
    }
    public void TurnAirCondition(bool val)
    {
        _isAirConditionTurnedOn = val;
        _isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }
    public void TurnHeat(bool val)
    {
        _isHeatTurnedOn = val;
        _isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }

    public async void SimulateRoomTemperature()
    {
        while (_tempSimulator)
        {
            if (_isAirConditionTurnedOn)
                _roomTemperature--;//Decrease Room Temperature if AC is turned On
            if (_isHeatTurnedOn)
                _roomTemperature++;//Decrease Room Temperature if AC is turned On
            System.Console.WriteLine("Temperature :" + _roomTemperature);
            if (WhenRoomTemperatureChange != null)
                WhenRoomTemperatureChange(_roomTemperature);
            System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
        }
    }

}

public class MySweetHome
{
    RoomTemperatureController roomController = null;
    public MySweetHome()
    {
        roomController = new RoomTemperatureController();
        roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
        //roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
        //roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
        roomController.SimulateRoomTemperature();
        System.Threading.Thread.Sleep(5000);
        roomController.TurnAirCondition (true);
        roomController.TurnRoomTeperatureSimulator = true;

    }
    public void TurnHeatOrACBasedOnTemp(int temp)
    {
        if (temp >= 30)
            roomController.TurnAirCondition(true);
        if (temp <= 15)
            roomController.TurnHeat(true);

    }
    public static void Main(string []args)
    {
        MySweetHome home = new MySweetHome();
    }


}

Covariance و Contravariance توفير مرونة إضافية لكائنات المفوض.ومن ناحية أخرى، فإن الحدث ليس لديه مثل هذه المفاهيم.

  • Covariance يسمح لك بتعيين طريقة للمندوب حيث يكون نوع إرجاع الطريقة فئة مشتقة من الفئة التي تحدد نوع الإرجاع للمندوب.
  • Contravariance يسمح لك بتعيين طريقة للمندوب حيث يكون نوع المعلمة للطريقة هو فئة أساسية للفئة التي يتم تحديدها كمعلمة للمندوب.

المندوب هو مؤشر دالة آمنة للنوع.الحدث عبارة عن تطبيق لنمط تصميم الناشر والمشترك باستخدام المفوض.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top