Зачем нам нужно ключевое слово «событие» при определении событий?


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. это приведет к сбою вашего базового кода. Ключевое слово event заставляет пользователя назначать что-то похожее на делегирование с помощью + =.
— Sumit Kapadia

Ответы:


142

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

Событие принципиально похоже на свойство - это пара методов добавления / удаления (вместо получения / установки свойства). Когда вы объявляете событие, подобное полю (то есть такое, в котором вы сами не указываете биты добавления / удаления), создается публичное событие и закрытое резервное поле. Это позволяет вам инициировать событие в частном порядке, но разрешить публичную подписку. С общедоступным полем делегата любой может удалить обработчики событий других людей, инициировать событие самостоятельно и т. Д. - это катастрофа инкапсуляции.

Подробнее о мероприятиях (и делегатах) читайте в моей статье по этой теме . (В какой-то момент мне нужно обновить это для C # 4, который очень незначительно меняет события, подобные полям. Однако суть этого все еще верна.)


17
Это в тысячу раз лучше, чем официальное однострочное объяснение MSDN: «Ключевое слово события используется для объявления события в классе издателя».
— cowlinator

37

Ключевое слово event выполняет 3 разные функции:

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

2
вы> MSDN. Спасибо.
— M. Azyoksul

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ключевое слово, инкапсуляция нарушится. Если это всего лишь публичный делегат многоадресной рассылки, любой может вызвать его, установить для него значение null или вмешаться в него. Если вызываемый класс MailNotifierсуществует и у него есть вызванное событие MailReceived, для других типов нет смысла запускать это событие посредством вызоваmailNotifier.MailReceived() ;

С другой стороны, вы можете вмешиваться и вызывать события типа «field like» только того типа, который его определил.

Если вы хотите, чтобы вызов события оставался закрытым, ничто не мешает вам делать что-то вроде этого:

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. После изменения модификатором события экземпляр делегата становится новым понятием «Событие». Итак, Event - это всего лишь один измененный делегат. Вы не можете напрямую изменить ссылку или вызвать событие вне класса, в котором событие было определено, но вы можете изменить ссылку или вызвать обычный экземпляр делегата. Событие обеспечивает дополнительную защиту, так что событие имеет больше функций безопасности. Когда вы находитесь вне класса, в котором было определено событие, вам разрешается выполнять два вида операций с событием: «+ =» и «- =». Но вы можете получить доступ ко всем общедоступным полям, свойствам, методам и т. Д. Обычного экземпляра делегата. Вот один пример:

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.