Навіщо нам потрібне ключове слово "подія" під час визначення подій?


109

Я не розумію, навіщо нам потрібне ключове слово "подія" під час визначення подій, коли ми можемо зробити те ж саме, не використовуючи ключове слово "подія", просто використовуючи делегатів.

напр

public delegate void CustomEventHandler(int a, string b);
public event CustomEventHandler customEvent;
customEvent += new CustomEventHandler(customEventHandler);
customEvent(1,"a"); // Raising the event

Тут, якщо я видаляю ключове слово "подія" з другого рядка, тоді також я можу підняти подію, викликаючи делегата. Хто-небудь, будь ласка, скажіть мені, для чого потрібне це ключове слово?


Гаразд, якщо ви не використовуєте ключове слово події будь-хто, хто може отримати доступ до цієї події за допомогою об’єкта класу, встановіть його на NULL, як objClass.SelectedIndexChanged = null. це знищить ваш базовий код. ключове слово події змушує користувача призначити щось подібне до делегата за допомогою + =.
Суміт Кападія

Відповіді:


142

Поле подібних подій і громадських полів типів делегатів виглядають схожими, але насправді дуже різні.

Подія принципово нагадує властивість - це пара методів додавання / видалення (замість get / set властивості). Коли ви оголошуєте подію, що нагадує поле (тобто ту, де ви самі не вказуєте додавання / видалення бітів), створюється публічна подія та приватне резервне поле. Це дозволяє підняти подію приватно, але дозволити публічну підписку. За допомогою публічного поля делегата кожен може видалити обробників подій інших людей, сам підняти подію тощо - це катаклізм катастрофи.

Детальніше про події (та делегатів) читайте мою статтю на цю тему . (У якийсь момент мені потрібно оновити це для C # 4, що дуже незначно змінює польові події. Однак суть його все-таки правильна.)


17
Це в тисячу разів краще офіційного однорядкового пояснення MSDN: "Ключове слово подія використовується для оголошення події в класі видавця."
ковбасник

37

Ключове слово події робить 3 різні речі:

  1. Ви можете визначити подію в інтерфейсі, навіть якщо ви не можете визначити звичайні поля в інтерфейсах.
  2. Він змінює видимість операторів =та ()(присвоєння та виклик) на приватні, так що лише клас, що містить вміст, може викликати подію або перекрити всі містяться в ній методи. Оператори -=та +=досі можуть викликати події поза класом, що визначає її (вони отримують модифікатор доступу, який ви написали поруч із подією).
  3. Ви також можете перекрити спосіб -=і +=поводитись на події.

2
ви> MSDN. Дякую.
М. Азіоксул

26

Інші відповіді чудові; Я просто хотів би додати щось інше, щоб подумати.

Ваше запитання: "навіщо нам потрібні події, коли ми маємо поля делегатного типу?" Я хотів би розширити це питання: навіщо вам потрібні методи, властивості, події, конструктори примірників або фіналізатори, якщо у вас є поля типу делегата? Навіщо вам потрібне що- небудь, крім полів, що містять значення та делегати типу? Чому б просто не сказати

class C
{
    private int z;
    public readonly Func<int, int> M = (int x)=>{ return x+z; }
    // ... and so on
}

?

Вам не потрібні методи, властивості чи події. Ми надаємо вам цей матеріал, оскільки метод, властивості та модель дизайну подій є важливими та корисними, і вони заслуговують на стандартний, задокументований, чіткий спосіб їх застосування в мові.


4
Оце Так! Це нагадує, чому я люблю c #! З усіх мов, з якими я працював, він має правильний баланс компактності, гнучкості та читабельної семантики. Єдина порівнянна мова - Object Pascal.
ATL_DEV

1
@ATL_DEV: Для цього є причина. Архітектор мови C #, Андерс Хейльсберг, раніше був архітектором Delphi, який був мовою, заснованою на Object Pascal.
Ерік Ліпперт

9

Частково це потрібно, тому що якщо ви пропустите eventключове слово, воно порушує інкапсуляцію. Якщо це просто публічний делегат багатоадресної передачі, будь-хто може викликати його, встановити його на нуль або підробити. Якщо виклик класу MailNotifierіснує, і він має покликану подію MailReceived, для інших типів немає сенсу створювати цю подію за допомогою виклику mailNotifier.MailReceived();

З іншого боку, ви можете лише втручатися та викликати події типу "поле типу" від типу, який його визначив.

Якщо ви хотіли зберегти виклик події приватним, нічого не заважає робити щось подібне:

public class MyClassWithNonFieldLikeEvent
{
   private CustomEventHandler m_delegate;

   public void Subscribe(CustomEventHandler handler) 
   {
      m_delegate += handler;        
   }

   public void Unsubscribe(CustomEventHandler handler)
   {          
      m_delegate -= handler;
   }

   private void DoSomethingThatRaisesEvent()
   {
      m_delegate.Invoke(...);
   }       
}

... але це ціле навантаження коду просто для того, щоб (більш-менш) робити те, що події, схожі на місця, вже дають нам.


Так само важче буде використовувати такі речі, як дизайнери ... ви в основному покладаєтесь на іменування конвенцій для методів, замість того, щоб публічні метадані говорили "це подія".
Джон Скіт

3

Події мають чіткі переваги порівняно з полями-делегатами. Події можна визначати в інтерфейсах на відміну від полів, додаючи абстракцію до коду, а ще важливіше: події можна викликати лише зсередини класу, що визначає. У вашому випадку будь-хто може зателефонувати на подію, можливо, знищивши ваш код.

Дивіться цю публікацію в блозі для отримання додаткової інформації.


2
Вам не слід порівнювати події та делегатів - порівнюйте події та загальнодоступні поля з типом делегата . І ні, рамки не вимагають, щоб події мали цей підпис. Ви можете створити подію будь-якого типу делегата, який вам подобається.
Джон Скіт

3

делегат - еталонний тип. Він успадковує MulticastDelegate . подія - Модифікатор. подіяє спеціальним модифікатором для делегата. Це змінює деякі функції / доступність методу, наприклад метод Invoke. Після зміни події модифікатора, екземпляр делегата стає новою концепцією "Подія". Тож подія - лише один модифікований делегат Ви не можете безпосередньо змінити посилання або викликати подію за межами класу, де визначена подія, але ви можете змінити посилання або викликати звичайний екземпляр делегата. Подія надає додатковий захист, щоб події мали більше функцій безпеки. Якщо ви перебуваєте за межами класу, де визначена подія, вам дозволяється робити два види операцій із подією, "+ =" та "- =". Але ви можете отримати доступ до всіх публічних полів, властивостей, методів тощо звичайного екземпляра делегата. Ось один із прикладів:

namespace DelegateEvent
{
    //the following line behave as a class. It is indeed a reference type
    public delegate void MyDelegate(string inputs);

    //The following line is illegal. It can only be an instance. so it cannot be directly under namespace
    //public event MyDelegate MyEvent;


    public class MyClassA
    {
        public event MyDelegate MyEventA;
        public MyDelegate MyDelegateA;


        System.Threading.ManualResetEvent MyResetEvent = new System.Threading.ManualResetEvent(false);
        public void TryToDoSomethingOnMyDelegateA()
        {
            if (MyDelegateA != null)
            {
                //User can assecc all the public methods.
                MyDelegateA("I can invoke detegate in classA");         //invoke delegate
                MyDelegateA.Invoke("I can invoke detegate in classA");  //invoke delegate
                IAsyncResult result = MyDelegateA.BeginInvoke("I can invoke detegate in classA", MyAsyncCallback, MyResetEvent);    //Async invoke
                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = MyDelegateA.Method;

                MyDelegateA = testMethod;                   //reset reference
                MyDelegateA = new MyDelegate(testMethod);   //reset reference
                MyDelegateA = null;                         //reset reference


                MyDelegateA += testMethod;                  //Add delegate
                MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                MyDelegateA -= testMethod;                  //Remove delegate
                MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        public void TryToDoSomethingOnMyEventA()
        {
            if (MyEventA != null)
            {
                MyEventA("I can invoke Event in classA");           //invoke Event
                MyEventA.Invoke("I can invoke Event in classA");    //invoke Event
                IAsyncResult result = MyEventA.BeginInvoke("I can invoke Event in classA", MyAsyncCallback, MyResetEvent);      //Async invoke
                //user can check the public properties and fields of MyEventA
                System.Reflection.MethodInfo delegateAMethodInfo = MyEventA.Method;


                MyEventA = testMethod;                   //reset reference
                MyEventA = new MyDelegate(testMethod);   //reset reference
                MyEventA = null;                         //reset reference


                MyEventA += testMethod;                  //Add delegate
                MyEventA += new MyDelegate(testMethod);  //Add delegate
                MyEventA -= testMethod;                  //Remove delegate
                MyEventA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        private void MyAsyncCallback(System.IAsyncResult result)
        {
            //user may do something here
        }
        private void testMethod(string inputs)
        {
            //do something
        }

    }
    public class MyClassB
    {
        public MyClassB()
        {
            classA = new MyClassA();
        }
        public MyClassA classA;
        public string ReturnTheSameString(string inputString)
        {
            return inputString;
        }


        public void TryToDoSomethingOnMyDelegateA()
        {
            if (classA.MyDelegateA != null)
            {
                //The following two lines do the same job --> invoke the delegate instance
                classA.MyDelegateA("I can invoke delegate which defined in class A in ClassB");
                classA.MyDelegateA.Invoke("I can invoke delegate which defined in class A in ClassB");
                //Async invoke is also allowed

                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = classA.MyDelegateA.Method;

                classA.MyDelegateA = testMethod;                   //reset reference
                classA.MyDelegateA = new MyDelegate(testMethod);   //reset reference
                classA.MyDelegateA = null;                         //reset reference


                classA.MyDelegateA += testMethod;                  //Add delegate
                classA.MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                classA.MyDelegateA -= testMethod;                  //Remove delegate
                classA.MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate

            }

        }
        public void TryToDoSomeThingMyEventA()
        {
            //check whether classA.MyEventA is null or not is not allowed
            //Invoke classA.MyEventA is not allowed
            //Check properties and fields of classA.MyEventA is not allowed
            //reset classA.MyEventA reference is not allowed

            classA.MyEventA += testMethod;                  //Add delegate
            classA.MyEventA += new MyDelegate(testMethod);  //Add delegate
            classA.MyEventA -= testMethod;                  //Remove delegate
            classA.MyEventA -= new MyDelegate(testMethod);  //Remove delegate
        }

        private void testMethod(string inputs)
        {
            //do something here
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.