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

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

public void EventName(object sender, EventArgs e);

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

Ответ 1

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

Центральная концепция делегата - это его подпись или форма. То есть (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 list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

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

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

Ответ 2

С# знает два термина, delegate и event. Начнем с первого.

делегат

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

MyClass instance = myFactory.GetInstance();

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

Action myMethod = myFactory.GetInstance;

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

MyClass instance = myMethod();

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

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

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

Итак, если у вас есть класс 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
   }

}

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

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

Еще одна большая разница между интерфейсом и делегатом заключается в том, что когда вы пишете свой класс, вам не нужно говорить С# "этот метод реализует этот тип делегата". С интерфейсами вам нужно сказать, что "этот класс реализует этот тип интерфейса".

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

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

Мероприятие

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

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

    Action myField;

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

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

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

Ява

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

Ответ 3

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

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 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();
    }
  }
}

Ответ 4

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

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, вы можете увидеть примеры событий, которые передают дополнительную информацию обработчикам событий.

Ответ 5

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

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

myObj.SomethingHappened += HandleSomethingHappened;

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

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

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

// -= removes the handler from the event 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.


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

SomethingHappened?.Invoke("Hi there!");

Ответ 6

Мое понимание событий:

Делегат:

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

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

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

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

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

  • Пожар события (вызов делегата)

Пример:

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();
            }
        }

    }   

}

Ответ 7

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

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

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

Ответ 8

//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 list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

Ответ 9

Я согласен с 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();
        }
    }

}   

}