it-roy-ru.com

Понимание событий и обработчиков событий в C #

Я понимаю цель событий, особенно в контексте создания пользовательских интерфейсов. Я думаю, что это прототип для создания события:

public void EventName(object sender, EventArgs e);

Что делают обработчики событий, зачем они нужны и как их создать?

286
Levi Campbell

Чтобы понять обработчики событий, вам нужно понять делегаты . В C # вы можете рассматривать делегата как указатель (или ссылку) на метод. Это полезно, потому что указатель может быть передан как значение.

Центральным понятием делегата является его подпись или форма. Это (1) возвращаемый тип и (2) входные аргументы. Например, если мы создаем делегат void MyDelegate(object sender, EventArgs e), он может указывать только на методы, которые возвращают void и принимают object и EventArgs. Вроде как квадратное отверстие и квадратный колышек. Поэтому мы говорим, что эти методы имеют ту же сигнатуру или форму, что и делегат.

Итак, зная, как создать ссылку на метод, давайте подумаем о цели событий: мы хотим, чтобы какой-то код выполнялся, когда что-то происходит в другом месте системы - или «обрабатывал событие». Для этого мы создаем специальные методы для кода, который мы хотим выполнить. Склейка между событием и выполняемыми методами - это делегаты. Событие должно внутренне хранить «список» указателей на методы, вызываемые при возникновении события. * Конечно, чтобы иметь возможность вызывать метод, нам нужно знать, какие аргументы передать ему! Мы используем делегата в качестве «контракта» между событием и всеми конкретными методами, которые будут вызваны.

Таким образом, по умолчанию EventHandler (и многим нравится) представляет собой конкретную форму метода (опять же, void/object-EventArgs). Когда вы объявляете событие, вы говорите какую форму метода (EventHandler) будет вызывать это событие, указав делегата:

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(* Это ключ к событиям в .NET и очищает от "магии" - событие действительно, под прикрытием, просто список методов той же "формы". Список хранится там, где оно происходит. Когда событие «возбуждено», на самом деле это просто «пройти через этот список методов и вызвать каждый из них, используя эти значения в качестве параметров». Назначение обработчика событий - это более красивый и простой способ добавления вашего метода в этот список методов. быть названным).

577
Rex M

C # знает два термина: delegate и event. Давайте начнем с первого.

Делегат

delegate является ссылкой на метод. Также как вы можете создать ссылку на экземпляр:

MyClass instance = myFactory.GetInstance();

Вы можете использовать делегат для создания ссылки на метод:

Action myMethod = myFactory.GetInstance;

Теперь, когда у вас есть эта ссылка на метод, вы можете вызвать метод через ссылку:

MyClass instance = myMethod();

Но почему ты? Вы также можете просто позвонить myFactory.GetInstance() напрямую. В этом случае вы можете. Тем не менее, есть много случаев, когда нужно подумать о том, где вы не хотите, чтобы остальная часть приложения знала myFactory или вызывала myFactory.GetInstance() напрямую. 

Очевидным является то, что вы хотите иметь возможность заменить myFactory.GetInstance() на myOfflineFakeFactory.GetInstance() из одного центрального места (aka шаблон фабричного метода ).

Шаблон фабричного метода

Итак, если у вас есть класс TheOtherClass и ему нужно использовать функцию myFactory.GetInstance(), то вот как будет выглядеть код без делегатов (вам нужно сообщить TheOtherClass о типе вашей myFactory):

TheOtherClass toc;
//...
toc.SetFactory(myFactory);


class TheOtherClass
{
   public void SetFactory(MyFactory factory)
   {
      // set here
   }

}

Если вы используете делегатов, вам не нужно указывать тип моей фабрики:

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);


class TheOtherClass
{
   public void SetFactoryMethod(Action factoryMethod)
   {
      // set here
   }

}

Таким образом, вы можете передать делегат другому классу для использования, не раскрывая им ваш тип. Единственное, что вы выставляете, это подпись вашего метода (сколько у вас параметров и тому подобное). 

«Подпись моего метода», где я слышал это раньше? О да, интерфейсы !!! интерфейсы описывают сигнатуру целого класса. Представьте, что делегаты описывают сигнатуру только одного метода!

Другое большое различие между интерфейсом и делегатом состоит в том, что когда вы пишете свой класс, вам не нужно говорить на C # «этот метод реализует этот тип делегата». Что касается интерфейсов, вам нужно сказать «этот класс реализует этот тип интерфейса».

Кроме того, ссылка на делегат может (с некоторыми ограничениями, см. Ниже) ссылаться на несколько методов (называемых MulticastDelegate). Это означает, что при вызове делегата будет выполнено несколько явно присоединенных методов. Ссылка на объект всегда может ссылаться только на один объект. 

Ограничения для MulticastDelegate заключаются в том, что подпись (метод/делегат) не должна иметь никакого возвращаемого значения (void), а ключевые слова out и ref не используются в подписи. Очевидно, что вы не можете вызвать два метода, которые возвращают число и ожидают, что они вернут один и тот же номер. Как только подпись соответствует, делегат автоматически становится MulticastDelegate.

Событие

События - это просто свойства (например, get; set; свойства для полей экземпляра), которые предоставляют подписку делегату от других объектов. Эти свойства, однако, не поддерживают get; set ;. Вместо этого они поддерживают add; Удалить;

Таким образом, вы можете иметь:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }

Использование в пользовательском интерфейсе (WinForms, WPF, UWP и т.д.)

Итак, теперь мы знаем, что делегат является ссылкой на метод и что у нас может быть событие, которое позволит миру узнать, что они могут дать нам свои методы, на которые будут ссылаться наши делегаты, и мы являемся кнопкой пользовательского интерфейса, тогда: мы может попросить любого, кто интересуется, нажали ли на меня, зарегистрировать свой метод у нас (через событие, которое мы выставили). Мы можем использовать все те методы, которые были нам предоставлены, и ссылаться на них нашим делегатом. А потом мы будем ждать и ждать .... пока пользователь не придет и не нажмет на эту кнопку, у нас будет достаточно причин для вызова делегата. И поскольку делегат ссылается на все эти методы, данные нам, все эти методы будут вызваны. Мы не знаем, что делают эти методы, и мы не знаем, какой класс реализует эти методы. Все, что нас волнует, это то, что кто-то был заинтересован в том, чтобы нас нажимали, и дал нам ссылку на метод, который соответствует нашей желаемой подписи.

Джава

Такие языки, как Java, не имеют делегатов. Вместо этого они используют интерфейсы. Они делают так, чтобы попросить любого, кто заинтересован в том, чтобы «нас нажимали», реализовать определенный интерфейс (с помощью определенного метода, который мы можем вызвать), а затем предоставить нам весь экземпляр, который реализует интерфейс. Мы храним список всех объектов, реализующих этот интерфейс, и можем вызывать их «определенный метод, который мы можем вызвать» всякий раз, когда нас нажимают.

93
tofi9

Вот пример кода, который может помочь:

using System;
using System.Collections.Generic;
using System.Text;

namespace Event_Example
{
  // First we have to define a delegate that acts as a signature for the
  // function that is ultimately called when the event is triggered.
  // You will notice that the second parameter is of MyEventArgs type.
  // This object will contain information about the triggered event.

  public delegate void MyEventHandler(object source, MyEventArgs e);

  // This is a class which describes the event to the class that receives it.
  // An EventArgs class must always derive from System.EventArgs.

  public class MyEventArgs : EventArgs
  {
    private string EventInfo;

    public MyEventArgs(string Text) {
      EventInfo = Text;
    }

    public string GetInfo() {
      return EventInfo;
    }
  }

  // This next class is the one which contains an event and triggers it
  // once an action is performed. For example, lets trigger this event
  // once a variable is incremented over a particular value. Notice the
  // event uses the MyEventHandler delegate to create a signature
  // for the called function.

  public class MyClass
  {
    public event MyEventHandler OnMaximum;

    private int i;
    private int Maximum = 10;

    public int MyValue
    {
      get { return i; }
      set
      {
        if(value <= Maximum) {
          i = value;
        }
        else 
        {
          // To make sure we only trigger the event if a handler is present
          // we check the event to make sure it's not null.
          if(OnMaximum != null) {
            OnMaximum(this, new MyEventArgs("You've entered " +
              value.ToString() +
              ", but the maximum is " +
              Maximum.ToString()));
          }
        }
      }
    }
  }

  class Program
  {
    // This is the actual method that will be assigned to the event handler
    // within the above class. This is where we perform an action once the
    // event has been triggered.

    static void MaximumReached(object source, MyEventArgs e) {
      Console.WriteLine(e.GetInfo());
    }

    static void Main(string[] args) {
      // Now lets test the event contained in the above class.
      MyClass MyObject = new MyClass();
      MyObject.OnMaximum += new MyEventHandler(MaximumReached);
      for(int x = 0; x <= 15; x++) {
        MyObject.MyValue = x;
      }
      Console.ReadLine();
    }
  }
}
38
Gary Willoughby

Это на самом деле объявление для обработчика событий - метода, который будет вызываться при возникновении события. Чтобы создать событие, вы должны написать что-то вроде этого:

public class Foo
{
    public event EventHandler MyEvent;
}

И тогда вы можете подписаться на событие, как это:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

С OnMyEvent () определяется следующим образом:

private void OnMyEvent(object sender, EventArgs e)
{
    MessageBox.Show("MyEvent fired!");
}

Всякий раз, когда Foo срабатывает MyEvent, будет вызван ваш обработчик OnMyEvent.

Вам не всегда нужно использовать экземпляр EventArgs в качестве второго параметра. Если вы хотите включить дополнительную информацию, вы можете использовать класс, производный от EventArgs (EventArgs является основой по соглашению). Например, если вы посмотрите на некоторые из событий, определенных для Control в WinForms или FrameworkElement в WPF, вы можете увидеть примеры событий, которые передают дополнительную информацию обработчикам событий.

33
Andy

Просто чтобы добавить к существующим отличным ответам здесь - на основе кода в принятом, который использует delegate void MyEventHandler(string foo)...

Поскольку компилятор знает тип делегата события SomethingHappened , это:

myObj.SomethingHappened += HandleSomethingHappened;

Полностью эквивалентно:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

И обработчики также могут быть незарегистрированными с -= следующим образом:

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

Ради полноты вызов события может быть выполнен следующим образом, только в классе, которому принадлежит событие:

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
    handler("Hi there!");
}

Локальная для обработчика копия обработчика необходима для того, чтобы убедиться, что вызов является поточно-безопасным, иначе поток мог бы пойти и отменить регистрацию последнего обработчика для события сразу после того, как мы проверили, было ли оно null, и у нас получилось бы «весело» NullReferenceException там.


C # 6 ввел хорошую короткую руку для этого паттерна. Он использует нулевой оператор распространения. 

SomethingHappened?.Invoke("Hi there!");
21
Mathieu Guindon

Мое понимание событий таково;

Делегат:  

Переменная для хранения ссылки на метод/методы, которые будут выполнены. Это позволяет передавать методы как переменную.

Шаги для создания и вызова события:

  1. Событие является экземпляром делегата

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

  3. Назначьте метод/методы, которые будут выполняться при возникновении события (Вызов делегата)

  4. Запустить событие (Позвонить делегату)

Пример:

using System;

namespace test{
    class MyTestApp{
        //The Event Handler declaration
        public delegate void EventHandler();

        //The Event declaration
        public event EventHandler MyHandler;

        //The method to call
        public void Hello(){
            Console.WriteLine("Hello World of events!");
        }

        public static void Main(){
            MyTestApp TestApp = new MyTestApp();

            //Assign the method to be called when the event is fired
            TestApp.MyHandler = new EventHandler(TestApp.Hello);

            //Firing the event
            if (TestApp.MyHandler != null){
                TestApp.MyHandler();
            }
        }

    }   

}
11
KE50

издатель: где происходят события. Издатель должен указать, какой делегат используется классом, и сгенерировать необходимые аргументы, передать эти аргументы и самому делегату.

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

Поэтому, когда событие происходит в издателе, делегат получит некоторые аргументы события (данные и т.д.), Но издатель не знает, что произойдет со всеми этими данными. Подписчики могут создавать методы в своем собственном классе, чтобы отвечать на события в классе издателя, чтобы подписчики могли отвечать на события издателя.

3
rileyss
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;

//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);
2
Bilgi Sayar

Я согласен с KE50, за исключением того, что я рассматриваю ключевое слово «event» как псевдоним для «ActionCollection», так как событие содержит набор действий, которые необходимо выполнить (т. Е. Делегат).

using System;

namespace test{

class MyTestApp{
    //The Event Handler declaration
    public delegate void EventAction();

    //The Event Action Collection 
    //Equivalent to 
    //  public List<EventAction> EventActions=new List<EventAction>();
    //        
    public event EventAction EventActions;

    //An Action
    public void Hello(){
        Console.WriteLine("Hello World of events!");
    }
    //Another Action
    public void Goodbye(){
        Console.WriteLine("Goodbye Cruel World of events!");
    }

    public static void Main(){
        MyTestApp TestApp = new MyTestApp();

        //Add actions to the collection
        TestApp.EventActions += TestApp.Hello;
        TestApp.EventActions += TestApp.Goodbye;

        //Invoke all event actions
        if (TestApp.EventActions!= null){
            //this peculiar syntax hides the invoke 
            TestApp.EventActions();
            //using the 'ActionCollection' idea:
            // foreach(EventAction action in TestApp.EventActions)
            //     action.Invoke();
        }
    }

}   

}
0
user3902302

Отличные технические ответы в посте! Мне нечего технически добавить к этому.

Одной из основных причин появления новых функций в языках и программном обеспечении в целом является маркетинг или политика компании! :-) Это не должно быть недооценено!

Я думаю, что это относится в определенной степени к делегатам и событиям! я нахожу их полезными и добавляю ценность языку C #, но с другой стороны язык Java решил не использовать их! они решили, что все, что вы решаете с делегатами, вы уже можете решить с помощью существующих функций языка, то есть интерфейсов, например 

В 2001 году Microsoft выпустила платформу .NET и язык C # как конкурентное решение для Java, поэтому было хорошо иметь НОВЫЕ ФУНКЦИИ, которых нет в Java.

0
Siraf

Недавно я сделал пример использования событий в c # и разместил его в своем блоге. Я попытался сделать это как можно более понятным, с очень простым примером. В случае, если это может кому-нибудь помочь, вот оно: http://www.konsfik.com/using-events-in-csharp/

Он включает в себя описание и исходный код (с большим количеством комментариев) и в основном фокусируется на правильном (подобном шаблону) использовании событий и обработчиков событий.

Некоторые ключевые моменты:

  • События похожи на «подтипы делегатов», только более ограниченные (в хорошем смысле). Фактически объявление события всегда включает делегата (EventHandlers - это тип делегата).

  • Обработчики событий - это особые типы делегатов (вы можете думать о них как о шаблоне), которые вынуждают пользователя создавать события, имеющие определенную «подпись». Подпись имеет формат: (отправитель объекта, EventArgs Eventarguments).

  • Вы можете создать свой собственный подкласс EventArgs, чтобы включить любой тип информации, которую должно передать событие. Нет необходимости использовать EventHandlers при использовании событий. Вы можете полностью пропустить их и использовать вместо них своего представителя.

  • Одно ключевое отличие между использованием событий и делегатов заключается в том, что события могут вызываться только из класса, в котором они были объявлены, даже если они могут быть объявлены как открытые. Это очень важное различие, потому что оно позволяет выставлять ваши события так, чтобы они были «связаны» с внешними методами, и в то же время они были защищены от «внешнего злоупотребления».

0
konsfik