Noções básicas sobre eventos e manipuladores de eventos em C #

330

Entendo o objetivo dos eventos, especialmente no contexto da criação de interfaces com o usuário. Eu acho que este é o protótipo para criar um evento:

public void EventName(object sender, EventArgs e);

O que os manipuladores de eventos fazem, por que são necessários e como faço para criar um?

Levi Campbell
fonte
10
Conforme observado por @Andy, o trecho de código aqui descreve o método registrado no evento, não o próprio evento.
dthrasher 18/09/09

Respostas:

661

Para entender os manipuladores de eventos, você precisa entender os delegados . Em C # , você pode pensar em um delegado como um ponteiro (ou uma referência) para um método. Isso é útil porque o ponteiro pode ser passado como um valor.

O conceito central de um delegado é sua assinatura ou forma. Esse é (1) o tipo de retorno e (2) os argumentos de entrada. Por exemplo, se criarmos um delegado void MyDelegate(object sender, EventArgs e), ele poderá apontar apenas para métodos que retornam voide recebem um objecte EventArgs. Como um buraco quadrado e uma estaca quadrada. Então, dizemos que esses métodos têm a mesma assinatura ou forma que o delegado.

Portanto, sabendo como criar uma referência a um método, vamos pensar no propósito dos eventos: queremos que algum código seja executado quando algo acontecer em outro lugar do sistema - ou "manipular o evento". Para fazer isso, criamos métodos específicos para o código que queremos que seja executado. A cola entre o evento e os métodos a serem executados são os delegados. O evento deve armazenar internamente uma "lista" de ponteiros para os métodos a serem chamados quando o evento é gerado. * É claro que, para poder chamar um método, precisamos saber quais argumentos passar para ele! Usamos o delegado como o "contrato" entre o evento e todos os métodos específicos que serão chamados.

Portanto, o padrão EventHandler(e muitos outros semelhantes) representa uma forma específica de método (novamente, void / object-EventArgs). Quando você declara um evento, está dizendo qual forma de método (EventHandler) esse evento chamará, especificando um delegado:

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

(* Essa é a chave para eventos no .NET e retira a "mágica" - um evento é realmente, oculto, apenas uma lista de métodos da mesma "forma". A lista é armazenada onde o evento fica. Quando o evento é "gerado", é realmente apenas "percorra esta lista de métodos e chame cada um, usando esses valores como parâmetros". Atribuir um manipulador de eventos é apenas uma maneira mais bonita e fácil de adicionar seu método a essa lista de métodos ser chamado).

Rex M
fonte
24
E agora alguém pode explicar por que o evento é chamado EventHandler? De todas as convenções de nomeação confusas, este é o pior ...
Joel em Gö
37
@ Joel in Go, o evento não é chamado EventHandler - EventHandler é o contrato que o evento deve ter com quem se comunica. É como "string MyString" - a string está declarando o tipo. evento MyEventHandler TheEvent está declarando que qualquer pessoa que interaja com esse evento deve estar em conformidade com o contrato MyEventHandler. A convenção de manipulador é porque o contrato descreve principalmente como lidar com o evento.
Rex M
18
Como o evento foi disparado?
alchemical
17
@Rex M: obrigado pela primeira explicação coerente para "MyEventHandler" que eu já vi :)
Joel em Gö
10
Obrigado pela fase: "A cola entre o evento e os métodos a serem executados são os delegados.", Isso é realmente incrível.
Zionpi
103

C # conhece dois termos delegatee event. Vamos começar com o primeiro.

Delegar

A delegateé uma referência a um método. Assim como você pode criar uma referência a uma instância:

MyClass instance = myFactory.GetInstance();

Você pode usar um delegado para criar uma referência a um método:

Action myMethod = myFactory.GetInstance;

Agora que você tem essa referência a um método, pode chamá-lo através da referência:

MyClass instance = myMethod();

Mas por que você faria? Você também pode ligar myFactory.GetInstance()diretamente. Nesse caso, você pode. No entanto, há muitos casos em que você não deseja que o restante do aplicativo tenha conhecimento myFactoryou ligue myFactory.GetInstance()diretamente.

Uma óbvia é se você quer ser capaz de substituir myFactory.GetInstance()a myOfflineFakeFactory.GetInstance()de um lugar central (aka padrão Factory Method ).

Padrão de método de fábrica

Portanto, se você tem uma TheOtherClassclasse e precisa usar a myFactory.GetInstance(), é assim que o código será exibido sem delegados (você precisará informar TheOtherClasssobre o tipo da sua myFactory):

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


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

}

Se você usar delegados, não precisará expor o tipo da minha fábrica:

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


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

}

Assim, você pode atribuir um delegado a outra classe para usar, sem expor seu tipo a eles. A única coisa que você está expondo é a assinatura do seu método (quantos parâmetros você tem e quais).

"Assinatura do meu método", onde eu ouvi isso antes? O sim, interfaces !!! interfaces descrevem a assinatura de uma classe inteira. Pense nos delegados como descrevendo a assinatura de apenas um método!

Outra grande diferença entre uma interface e um delegado é que, quando você está escrevendo sua classe, não precisa dizer ao C # "esse método implementa esse tipo de delegado". Com interfaces, você precisa dizer "esta classe implementa esse tipo de interface".

Além disso, uma referência de delegado pode (com algumas restrições, veja abaixo) fazer referência a vários métodos (chamados MulticastDelegate). Isso significa que quando você chama o delegado, vários métodos anexados explicitamente serão executados. Uma referência a objeto sempre pode fazer referência apenas a um objeto.

As restrições para a MulticastDelegatesão que a assinatura (método / delegado) não deve ter nenhum valor de retorno ( void) e as palavras-chave oute refnão é usada na assinatura. Obviamente, você não pode chamar dois métodos que retornam um número e esperam que eles retornem o mesmo número. Uma vez que a assinatura esteja em conformidade, o delegado será automaticamente a MulticastDelegate.

Evento

Eventos são apenas propriedades (como os campos get; set; properties para instance) que expõem a assinatura ao delegado de outros objetos. Essas propriedades, no entanto, não suportam get; set ;. Em vez disso, eles suportam add; remover;

Então você pode ter:

    Action myField;

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

Uso na interface do usuário (WinForms, WPF, UWP etc.)

Portanto, agora sabemos que um delegado é uma referência a um método e que podemos ter um evento para informar ao mundo que eles podem nos fornecer seus métodos a serem referenciados por nosso delegado, e somos um botão de interface do usuário: pode perguntar a qualquer pessoa interessada se fui clicado para registrar seu método conosco (por meio do evento exposto). Podemos usar todos os métodos que nos foram dados e consultá-los pelo nosso delegado. E então, vamos esperar ... até que um usuário chegue e clique nesse botão, teremos motivos suficientes para chamar o delegado. E como o delegado faz referência a todos os métodos que nos foram dados, todos esses métodos serão invocados. Não sabemos o que esses métodos fazem, nem sabemos qual classe implementa esses métodos. Só nos preocupamos com o fato de alguém estar interessado em sermos clicados,

Java

Idiomas como Java não têm representantes. Eles usam interfaces. A maneira como eles fazem isso é pedir a qualquer pessoa interessada em 'nos clicar', para implementar uma certa interface (com um determinado método que podemos chamar), e nos fornecer toda a instância que implementa a interface. Mantemos uma lista de todos os objetos que implementam essa interface e podemos chamar seu 'método certo que podemos chamar' sempre que clicamos.

tofi9
fonte
aplaude a explicação, mas como um evento é diferente de uma instância de um delegado que aceita assinantes? ambos parecem exatamente a mesma coisa?
BKSpurgeon
@BKSpurgeon é porque eles são "delegados que aceitam assinantes" - eventé mero açúcar de sintaxe, nada mais.
Mathieu Guindon
"As restrições para um MulticastDelegate são que a assinatura (método / delegado) não deve ter nenhum valor de retorno (nulo)", não acho que isso esteja correto. Se eles tiverem valores de retorno, ele retornará o último.
Hozikimaru
"Assim, você pode atribuir um delegado a outra classe para usar, sem expor seu tipo a eles. A única coisa que você está expondo é a assinatura do seu método ..." - isso para mim é o ponto crítico. Obrigado!
Ryan
40

Essa é realmente a declaração de um manipulador de eventos - um método que será chamado quando um evento for disparado. Para criar um evento, escreva algo como isto:

public class Foo
{
    public event EventHandler MyEvent;
}

E então você pode se inscrever no evento assim:

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

Com OnMyEvent () definido assim:

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

Sempre que Foodisparar MyEvent, seu OnMyEventmanipulador será chamado.

Você nem sempre precisa usar uma instância EventArgscomo o segundo parâmetro. Se você deseja incluir informações adicionais, pode usar uma classe derivada de EventArgs( EventArgsé a base por convenção). Por exemplo, se você observar alguns dos eventos definidos Controlno WinForms ou FrameworkElementno WPF, poderá ver exemplos de eventos que transmitem informações adicionais aos manipuladores de eventos.

Andy
fonte
14
Obrigado por responder à pergunta e não participar dos Delegados e Eventos.
Div_byzero
3
Eu recomendaria não usar o OnXXXpadrão de nomeação para seu manipulador de eventos. (Estupidamente, OnXXX é entendido como 'manipular XXX' no MFC e 'aumentar XXX' em .net, e agora seu significado não é claro e confuso - consulte esta publicação para obter detalhes ). Os nomes preferidos seriam RaiseXXXgerar eventos e / HandleXXXou Sender_XXXpara manipuladores de eventos.
Jason Williams
1
Você pode mostrar um exemplo de trabalho com um aplicativo WinForms simples?
MC9000
40

Aqui está um exemplo de código que pode ajudar:

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();
    }
  }
}
Gary Willoughby
fonte
4
A chamada de delegado pode no C # 6 ser simplificada para:OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
Tim Schmelter
23

Apenas para adicionar as ótimas respostas existentes aqui - com base no código do aceito, que usa um delegate void MyEventHandler(string foo)...

Como o compilador conhece o tipo de delegado do evento SomethingHappened , isso:

myObj.SomethingHappened += HandleSomethingHappened;

É totalmente equivalente a:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

E os manipuladores também podem ser não registrados com o -=seguinte:

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

Por uma questão de completude, aumentar o evento pode ser feito assim, apenas na classe proprietária do evento:

//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!");
}

A cópia local do encadeamento do manipulador é necessária para garantir que a chamada seja segura para encadeamento - caso contrário, um encadeamento poderá cancelar o registro do último manipulador do evento imediatamente depois de verificarmos se foi nulle teríamos uma "diversão" NullReferenceExceptionlá .


O C # 6 apresentou uma boa mão curta para esse padrão. Ele usa o operador de propagação nula.

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

Minha compreensão dos eventos é;

Delegar:

Uma variável para conter referência ao método / métodos a serem executados. Isso torna possível passar métodos como uma variável.

Etapas para criar e chamar o evento:

  1. O evento é uma instância de um delegado

  2. Como um evento é uma instância de um delegado, primeiro precisamos definir o delegado.

  3. Atribua o método / métodos a serem executados quando o evento for disparado ( chamando o delegado )

  4. Dispare o evento ( ligue para o delegado )

Exemplo:

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

    }   

}
KE50
fonte
3

editor: onde os eventos acontecem. O Publicador deve especificar qual delegado a classe está usando e gerar argumentos necessários, passar esses argumentos e ele próprio para o delegado.

assinante: onde a resposta acontece. O assinante deve especificar métodos para responder a eventos. Esses métodos devem usar o mesmo tipo de argumento que o delegado. O assinante adiciona esse método ao delegado do editor.

Portanto, quando o evento ocorrer no editor, o delegado receberá alguns argumentos do evento (dados, etc.), mas o editor não tem idéia do que acontecerá com todos esses dados. Os assinantes podem criar métodos em sua própria classe para responder a eventos na classe do editor, para que os assinantes possam responder aos eventos do editor.

rileyss
fonte
2
//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);
Bilgi Sayar
fonte
0

Concordo com o KE50, exceto que vejo a palavra-chave 'event' como um alias para 'ActionCollection', já que o evento contém uma coleção de ações a serem executadas (por exemplo, o delegado).

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

}   

}
user3902302
fonte
0

Ótimas respostas técnicas no post! Tecnicamente, não tenho nada a acrescentar.

Uma das principais razões pelas quais os novos recursos aparecem nos idiomas e no software em geral é o marketing ou a política da empresa! :-) Isso não deve ser subestimado!

Eu acho que isso se aplica em certos casos aos delegados e eventos também! Acho-os úteis e agrego valor à linguagem C #, mas, por outro lado, a linguagem Java decidiu não usá-las! eles decidiram que o que você está resolvendo com os delegados já pode resolver com os recursos existentes da linguagem, ou seja, interfaces, por exemplo

Por volta de 2001, a Microsoft lançou o framework .NET e a linguagem C # como uma solução concorrente para Java, então era bom ter NOVOS RECURSOS que o Java não possui.

Siraf
fonte
0

Recentemente, fiz um exemplo de como usar eventos em c # e publiquei no meu blog. Tentei deixar o mais claro possível, com um exemplo muito simples. Caso isso possa ajudar alguém, aqui está: http://www.konsfik.com/using-events-in-csharp/

Inclui descrição e código fonte (com muitos comentários), e se concentra principalmente no uso adequado (semelhante ao modelo) de eventos e manipuladores de eventos.

Alguns pontos-chave são:

  • Os eventos são como "subtipos de delegados", apenas mais restritos (no bom sentido). De fato, a declaração de um evento sempre inclui um delegado (EventHandlers é um tipo de delegado).

  • Manipuladores de eventos são tipos específicos de delegados (você pode pensar neles como um modelo), que forçam o usuário a criar eventos com uma "assinatura" específica. A assinatura está no formato: (remetente do objeto, EventArgs eventarguments).

  • Você pode criar sua própria subclasse de EventArgs, a fim de incluir qualquer tipo de informação que o evento precise transmitir. Não é necessário usar EventHandlers ao usar eventos. Você pode ignorá-los completamente e usar seu próprio tipo de delegado no lugar deles.

  • Uma diferença importante entre o uso de eventos e delegados é que os eventos só podem ser chamados de dentro da classe em que foram declarados, mesmo que possam ser declarados como públicos. Essa é uma distinção muito importante, pois permite que seus eventos sejam expostos para que sejam "conectados" a métodos externos, enquanto ao mesmo tempo eles são protegidos contra "uso indevido externo".

konsfik
fonte
0

Outra coisa a saber , em alguns casos, você deve usar os Delegados / Eventos quando precisar de um baixo nível de acoplamento !

Se você deseja usar um componente em vários locais do aplicativo , é necessário criar um componente com baixo nível de acoplamento e a LÓGICA despreocupada específica deve ser delegada FORA do seu componente! Isso garante que você tenha um sistema desacoplado e um código mais limpo.

No princípio SOLID , este é o " D ", ( princípio de inversão da dependência de D ).

Também conhecido como " IoC ", Inversão de controle .

Você pode criar " IoC " com eventos, delegados e DI (injeção de dependência).

É fácil acessar um método em uma classe filho. Mas é mais difícil acessar um método em uma classe pai a partir de filho. Você precisa passar a referência dos pais para a criança! (ou use DI com interface)

Delegados / Eventos nos permitem comunicar da criança para os pais sem referência!

insira a descrição da imagem aqui

Neste diagrama acima, eu não uso Delegado / Evento e o componente pai B deve ter uma referência do componente pai A para executar a lógica de negócios despreocupada no método A. (alto nível de acoplamento)

Com essa abordagem, eu teria que colocar todas as referências de todos os componentes que usam o componente B! :(

insira a descrição da imagem aqui

Neste diagrama acima, eu uso Delegate / Event e o componente B não precisa conhecer A. (baixo nível de acoplamento)

E você pode usar seu componente B em qualquer lugar do seu aplicativo !

A. Morel
fonte