Noções básicas sobre events e manipuladores de events em c #

Eu entendo o propósito dos events, especialmente dentro do contexto de criação de interfaces de usuário. Eu acho que esse é o protótipo para criar um evento:

public void EventName(object sender, EventArgs e); 

O que os manipuladores de events fazem, por que são necessários e como criar um?

Para entender os manipuladores de events, você precisa entender os delegates . 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. Isso é (1) o tipo de retorno e (2) os argumentos de input. Por exemplo, se criarmos um delegado void MyDelegate(object sender, EventArgs e) , ele só poderá apontar para methods que retornam void e obter um object e EventArgs . Tipo como um buraco quadrado e um peg quadrado. Então, dizemos que esses methods têm a mesma assinatura ou forma do delegado.

Assim, sabendo como criar uma referência a um método, vamos pensar sobre o propósito dos events: queremos fazer com que algum código seja executado quando algo acontece em outro lugar do sistema – ou “manipular o evento”. Para fazer isso, criamos methods específicos para o código que queremos executar. A cola entre o evento e os methods a serem executados são os delegates. O evento deve armazenar internamente uma “lista” de pointers para os methods 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 methods específicos que serão chamados.

Portanto, o EventHandler padrão (e muitos como ele) representa uma forma específica de método (novamente, void / object-EventArgs). Quando você declara um evento, você está dizendo qual forma de método (EventHandler) esse evento invocará, 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"); 

(* Esta é a chave para events no .NET e descarta a “mágica” – um evento é, na verdade, apenas uma lista de methods da mesma “forma”. A lista é armazenada onde o evento se encontra. o evento é “criado”, é apenas “passar por essa lista de methods e chamar cada um deles, usando esses valores como parâmetros”. Atribuir um manipulador de events é apenas uma maneira mais bonita e fácil de adicionar seu método a essa lista de methods. ser chamado).

C # conhece dois termos, delegate e event . Vamos começar com o primeiro.

Delegar

Um delegate é uma referência a um método. Assim como você pode criar uma referência para 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 esta referência a um método, você pode chamar o método através da referência:

 MyClass instance = myMethod(); 

Mas por que você faria isso? Você também pode apenas chamar myFactory.GetInstance() diretamente. Neste caso você pode. No entanto, há muitos casos em que você não quer que o restante do aplicativo tenha conhecimento de myFactory ou que chame myFactory.GetInstance() diretamente.

Uma óbvia é se você deseja replace myFactory.GetInstance() em myOfflineFakeFactory.GetInstance() de um local central (também conhecido como padrão de método de fábrica ).

Padrão de método de fábrica

Então, se você tem uma class TheOtherClass e precisa usar o myFactory.GetInstance() , é assim que o código ficará sem delegates (você precisará informar ao TheOtherClass sobre o tipo do seu myFactory ):

 TheOtherClass toc; //... toc.SetFactory(myFactory); class TheOtherClass { public void SetFactory(MyFactory factory) { // set here } } 

Se você usasse delegates, não teria que 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 dar um delegado a alguma outra class 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 tal).

“Assinatura do meu método”, onde eu ouvi isso antes? O sim, interfaces !!! interfaces descrevem a assinatura de uma class inteira. Pense nos delegates 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 class, você não precisa dizer ao C # “este método implementa esse tipo de delegado”. Com as interfaces, você precisa dizer “essa class implementa esse tipo de interface”.

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

As restrições para um MulticastDelegate são que a assinatura (método / delegado) não deve ter nenhum valor de retorno ( void ) e as palavras out chave out e ref não são usadas na assinatura. Obviamente, você não pode chamar dois methods que retornam um número e esperar que eles retornem o mesmo número. Quando a assinatura estiver em conformidade, o delegado será automaticamente um MulticastDelegate .

Evento

Os events são apenas propriedades (como o get; set; propriedades para campos de instância) que expõem a assinatura ao delegado de outros objects. Essas propriedades, no entanto, não suportam get; set ;. Em vez disso, eles suportam add; remove;

Então você pode ter:

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

Uso na interface do usuário (WinForms)

Então, 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 methods para serem referenciados por nosso delegado, e nós somos um botão de interface do usuário, então: pode perguntar a qualquer um que esteja interessado em saber se eu fui clicado, para registrar seu método conosco (através do evento que expusemos). Podemos usar todos os methods que nos foram dados e referenciá-los pelo nosso delegado. E então, vamos esperar e esperar …. até que um usuário venha e clica nesse botão, então teremos motivos suficientes para chamar o delegado. E como o delegado faz referência a todos os methods que nos são fornecidos, todos esses methods serão invocados. Não sabemos o que esses methods fazem, nem sabemos qual class implementa esse método. Tudo o que nos interessa é que alguém tenha interesse em sermos clicados e nos forneça uma referência a um método que cumpra nossa assinatura desejada.

Java

Idiomas como o Java não têm delegates. Eles usam interfaces em vez disso. A maneira como eles fazem isso é perguntar a qualquer um que esteja interessado em ‘ser clicado’, para implementar uma determinada interface (com um determinado método que podemos chamar), então nos dê toda a instância que implementa a interface. Mantemos uma lista de todos os objects que implementam essa interface, e podemos chamar seu “método específico que podemos chamar” sempre que recebermos cliques.

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

Essa é, na verdade, a declaração para um manipulador de events – um método que será chamado quando um evento for triggersdo. Para criar um evento, você escreveria algo assim:

 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 Foo triggersr MyEvent , seu manipulador OnMyEvent será chamado.

Você nem sempre precisa usar uma instância de EventArgs como o segundo parâmetro. Se você quiser include informações adicionais, poderá usar uma class derivada de EventArgs ( EventArgs é a base por convenção). Por exemplo, se você observar alguns dos events definidos em Control no WinForms ou FrameworkElement no WPF, poderá ver exemplos de events que transmitem informações adicionais aos manipuladores de events.

Apenas para adicionar as grandes respostas existentes aqui – construindo o código no código aceito, que usa um delegate void MyEventHandler(string foo)

Porque 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 cancelados com -= assim:

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

Para completar, aumentar o evento pode ser feito assim, somente na class que possui o 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 de thread local do manipulador é necessária para garantir que a invocação seja thread-safe – caso contrário, um thread poderia ir e cancelar o registro do último manipulador para o evento imediatamente depois de verificarmos se era null , e teríamos uma “diversão” NullReferenceException lá.


C # 6 introduziu um bom ponteiro curto para este padrão. Ele usa o operador de propagação nula.

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

Minha compreensão dos events é;

Delegar:

Uma variável para manter referência ao método / methods a serem executados. Isso possibilita a transmissão de methods 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, precisamos primeiro definir o delegado.

  3. Atribuir o método / methods a serem executados quando o evento for triggersdo ( Chamando o delegado )

  4. Incêndio do 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(); } } } } 

editora: onde os events acontecem. O Publisher deve especificar qual delegado a class está usando e gerar os argumentos necessários, passar esses argumentos e a si mesmo para o delegado.

assinante: onde a resposta acontece. O assinante deve especificar methods para responder aos events. Esses methods devem ter o mesmo tipo de argumentos que o delegado. Assinante, em seguida, adicione este método ao delegado do editor.

Portanto, quando o evento acontecer 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 inscritos podem criar methods em sua própria turma para responder a events na turma do editor, para que os inscritos possam responder aos events do editor.

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

Eu concordo com o KE50, exceto que eu 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 (isto é, o delegado).

 using System; namespace test{ class MyTestApp{ //The Event Handler declaration public delegate void EventAction(); //The Event Action Collection //Equivalent to // public List EventActions=new List(); // 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(); } } } }