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

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

public void EventName(object sender, EventArgs e);

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

9 ответов


чтобы понять обработчики событий, вам нужно понять представители. В 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 и снимает "магию" - событие действительно, под обложками, просто список методов той же"формы". Список хранится там, где живет событие. Когда событие "поднято", это действительно просто"пройти через этот список методов и вызвать каждый из них, используя эти значения в качестве параметров". Назначение события handler-это просто более красивый и простой способ добавления вашего метода в этот список вызываемых методов).


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

делегат

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

MyClass instance = myFactory.GetInstance();

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

Action myMethod = myFactory.GetInstance;

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

MyClass instance = myMethod();

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

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

метод фабрики шаблон

Итак, если у вас есть TheOtherClass класс и он должен использовать myFactory.GetInstance(), вот как будет выглядеть код без делегатов (вам нужно будет let 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; properties to instance), которые предоставляют подписку делегату из других объектов. Эти свойства, однако, не поддерживают get; set;. Вместо этого они поддерживают add; remove;

так что вы можете иметь:

    Action myField;

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

использование в UI (Приложения WinForms)

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

Java

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


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

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

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

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


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

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

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 представил хорошую короткую руку для этого шаблона. Он использует оператор распространения null.

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

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

делегат:

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

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

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

    }   

}

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

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

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


//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);

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

}   

}