Diferença entre eventos e delegados e suas respectivas aplicações [fechado]

107

Não vejo vantagens em usar eventos sobre delegados, além de ser um açúcar sintático. Talvez eu esteja entendendo mal, mas parece que o evento é apenas um espaço reservado para delegado.

Você poderia me explicar as diferenças e quando usá-las? Quais são as vantagens e desvantagens? Nosso código está fortemente enraizado em eventos e quero chegar ao fundo disso.

Quando você usaria delegados em eventos e vice-versa? Informe sua experiência no mundo real com ambos, digamos, no código de produção.

halfer
fonte
Sim, envolver minha cabeça em torno das diferenças foi realmente difícil, elas parecem iguais e parecem fazer o mesmo à primeira vista
Robert Gould
1
Veja também esta questão .
Dimitri C.
1
A diferença entre dois eventos e delegados é uma questão de fato, não de opinião. A questão pede as respectivas aplicações porque ilustram a diferença nos problemas que as tecnologias resolvem. Isso também não é uma questão de opinião porque ninguém perguntou o que era melhor. Nenhuma parte desta questão é uma questão de opinião, e esta declaração também não é uma opinião. Na minha opinião. Você conseguiu seu distintivo?
Peter Wone

Respostas:

49

Do ponto de vista técnico, outras respostas abordaram as diferenças.

De uma perspectiva semântica, os eventos são ações geradas por um objeto quando certas condições são atendidas. Por exemplo, minha classe Stock tem uma propriedade chamada Limit e gera um evento quando o preço das ações atinge o Limit. Esta notificação é feita por meio de um evento. Se alguém realmente se preocupa com este evento e se inscreve nele está além da preocupação da classe do proprietário.

Um delegado é um termo mais genérico para descrever uma construção semelhante a um ponteiro em termos C / C ++. Todos os delegados em .Net são delegados multicast. De uma perspectiva semântica, eles geralmente são usados ​​como um tipo de entrada. Em particular, eles são uma maneira perfeita de implementar o Padrão de Estratégia . Por exemplo, se eu quiser classificar uma Lista de objetos, posso fornecer uma estratégia de Comparador para o método para dizer à implementação como comparar dois objetos.

Usei os dois métodos no código de produção. Toneladas de meus objetos de dados notificam quando certas propriedades são atendidas. Exemplo mais básico, sempre que uma propriedade é alterada, um evento PropertyChanged é gerado (consulte a interface INotifyPropertyChanged). Usei delegados no código para fornecer diferentes estratégias de transformar certos objetos em strings. Este exemplo específico foi uma lista ToString () glorificada de implementações para um tipo de objeto específico para exibi-lo aos usuários.

Szymon Rozga
fonte
4
Talvez eu esteja faltando alguma coisa, mas um Event Handler não é um tipo de delegado?
Powerlord
1
Minha resposta aborda as questões Edit # 1 e # 2; diferenças de uma perspectiva de uso. Para o propósito desta discussão, eles são diferentes, embora, do ponto de vista técnico, você esteja correto. Dê uma olhada nas outras respostas para diferenças técnicas.
Szymon Rozga
3
"Todos os delegados em .Net são delegados multicast"? Mesmo delegados que retornam valores?
Qwertie
5
Sim. Para obter o histórico, dê uma olhada em msdn.microsoft.com/en-us/magazine/cc301816.aspx . Confira: msdn.microsoft.com/en-us/library/system.delegate.aspx . Se eles retornarem valores, o valor retornado é a avaliação do último delegado na cadeia.
Szymon Rozga
delegados são tipos de referência que apontam para os manipuladores de eventos definidos na classe do assinante. Em outras palavras, o delegado é usado como um link entre o evento (no editor) e o manipulador de eventos definido no assinante. Em um aplicativo, haverá vários assinantes necessários para ouvir um evento e, em tais cenários, os delegados nos oferecem uma maneira eficiente de conectar o editor e os assinantes.
josepainumkal
55

A palavra event- chave é um modificador de escopo para delegados multicast. As diferenças práticas entre isso e apenas declarar um delegado multicast são as seguintes:

  • Você pode usar eventem uma interface.
  • O acesso de invocação ao delegado multicast é limitado à classe declarante. O comportamento é como se o delegado fosse privado para invocação. Para fins de atribuição, o acesso é conforme especificado por um modificador de acesso explícito (por exemplo public event).

Por uma questão de interesse, você pode aplicar +e -a delegados multicast, e esta é a base da sintaxe +=e -=para a atribuição de combinação de delegados a eventos. Esses três snippets são equivalentes:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Amostra dois, ilustrando a atribuição direta e a atribuição combinada.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Amostra três: sintaxe mais familiar. Você provavelmente está familiarizado com a atribuição de null para remover todos os manipuladores.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Assim como as propriedades, os eventos têm uma sintaxe completa que ninguém usa. Este:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... faz exatamente o mesmo:

class myExample 
{
  public event EventHandler OnSubmit;
}

Os métodos add e remove são mais evidentes na sintaxe bastante afetada que o VB.NET usa (sem sobrecargas de operador).

Peter Wone
fonte
6
+ para "O acesso de invocação ao delegado multicast é limitado à classe declarante" - isso para mim é o principal ponto de diferença entre delegados e eventos.
RichardOD
2
Outra diferença importante (mencionada por itowlson abaixo) é que não se pode cancelar a inscrição de todos os manipuladores de eventos atribuindo a um evento, mas eles poderiam fazer isso com um delegado. (A propósito, a sua resposta foi a mais útil para mim de todas).
Roman Starkov
4
Por mais úteis que o Google e o stackoverflow possam ser, tudo isso e muito mais está disponível em detalhes atordoantes nas especificações da linguagem C #, publicamente disponíveis gratuitamente na Microsoft. Eu sei que, aparentemente, Deus criou o manual e Jon Skeet o engoliu, mas há outras cópias :)
Peter Wone
12

Os eventos são açúcares sintáticos. Eles são deliciosos. Quando vejo um evento, sei o que fazer. Quando vejo um delegado, não tenho tanta certeza.

Combinar eventos com interfaces (mais açúcar) é um lanche de dar água na boca. Delegados e classes abstratas virtuais puras são muito menos atraentes.

Sean
fonte
é assim que eu também vejo. Eu quero uma explicação mais profunda e doce :)
13
Muito açúcar faz uma pessoa engordar, no entanto ... = P
Erik Forbes
5

Os eventos são marcados como tal nos metadados. Isso permite que coisas como Windows Forms ou ASP.NET designers diferenciem eventos de meras propriedades do tipo delegado e forneçam suporte apropriado para eles (especificamente mostrando-os na guia Eventos da janela Propriedades).

Outra diferença de uma propriedade do tipo delegado é que os usuários podem apenas adicionar e remover manipuladores de eventos, enquanto com uma propriedade do tipo delegado eles podem definir o valor:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Isso ajuda a isolar os assinantes do evento: posso adicionar meu manipulador a um evento e você pode adicionar seu manipulador ao mesmo evento, e você não substituirá acidentalmente meu manipulador.

Itowlson
fonte
4

Embora os eventos sejam normalmente implementados com delegados multicast, não há requisitos para que sejam usados ​​dessa maneira. Se uma classe expõe um evento, isso significa que a classe expõe dois métodos. Seus significados são, em essência:

  1. Aqui está um delegado. Invoque-o quando algo interessante acontecer.
  2. Aqui está um delegado. Você deve destruir todas as referências a ele assim que for conveniente (e não chamá-lo mais).

A maneira mais comum de uma classe manipular um evento que expõe é definir um delegado multicast e adicionar / remover qualquer delegado que seja passado para os métodos acima, mas não há nenhum requisito de que funcionem dessa forma. Infelizmente, a arquitetura do evento falha em fazer algumas coisas que teriam tornado as abordagens alternativas muito mais limpas (por exemplo, fazer com que o método de inscrição retornasse um MethodInvoker, que seria mantido pelo assinante; para cancelar a inscrição de um evento, simplesmente invoque o método retornado), portanto, delegados multicast são de longe a abordagem mais comum.

supergato
fonte
4

para entender as diferenças, você pode olhar para estes 2 exemplos

Exemplo com delegados (ação neste caso que é um tipo de delegado que não retorna valor)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

para usar o delegado, você deve fazer algo assim

Animale animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

este código funciona bem, mas você pode ter alguns pontos fracos.

Por exemplo, se eu escrever isso

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

com a última linha de código que substituí os comportamentos anteriores, apenas faltando um +(usei em +vez de +=)

Outro ponto fraco é que cada classe que usa sua Animalclasse pode aumentar RaiseEventapenas pagando animal.RaiseEvent().

Para evitar esses pontos fracos, você pode usar eventsem c #.

Sua classe Animal mudará desta forma

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

chamar eventos

 Animale animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

Diferenças:

  1. Você não está usando uma propriedade pública, mas um campo público (com eventos, o compilador protege seus campos de acesso indesejado)
  2. Os eventos não podem ser atribuídos diretamente. Nesse caso, você não pode fazer o erro anterior que mostrei ao substituir o comportamento.
  3. Ninguém fora de sua classe pode levantar o evento.
  4. Os eventos podem ser incluídos em uma declaração de interface, enquanto um campo não pode

notas

EventHandler é declarado como o seguinte delegado:

public delegate void EventHandler (object sender, EventArgs e)

leva um remetente (do tipo Object) e argumentos de evento. O remetente é nulo se vier de métodos estáticos.

Você também pode usar EventHAndlereste exemplo que usaEventHandler<ArgsSpecial>

consulte aqui a documentação sobre o EventHandler

faby
fonte
3

Editar # 1 Quando você usaria delegados sobre eventos e vs.versa? Informe sua experiência no mundo real com ambos, digamos, no código de produção.

Quando projeto minhas próprias APIs, defino delegados que são passados ​​como parâmetros para métodos ou para os construtores de classes:

  • Para que um método possa implementar um padrão simples de 'método de modelo' (como, por exemplo, os delegados Predicatee Actionsão passados ​​para as classes de coleção genérica .Net)
  • Ou para que a classe possa fazer um 'retorno de chamada' (normalmente um retorno de chamada para um método da classe que o criou).

Esses delegados geralmente não são opcionais em tempo de execução (ou seja, não devem ser null).

Eu tendo a não usar eventos; mas onde eu uso eventos, eu os uso opcionalmente para sinalizar eventos para zero, um ou mais clientes que possam estar interessados, ou seja, quando faz sentido que uma classe (por exemplo, oSystem.Windows.Form classe) deva existir e ser executada, quer qualquer cliente tenha ou não adicionou um manipulador de eventos ao seu evento (por exemplo, o evento 'mouse down' do formulário existe, mas é opcional se algum cliente externo está interessado em instalar um manipulador de eventos naquele evento).

ChrisW
fonte
2

Embora eu não tenha motivos técnicos para isso, eu uso eventos no código de estilo da IU, em outras palavras, nos níveis mais altos do código, e uso delegados para lógica que se estende mais profundamente no código. Como eu disse, você poderia usar qualquer um dos dois, mas acho que esse padrão de uso é lógico, pelo menos ajuda a documentar os tipos de retorno de chamada e sua hierarquia também.


Edit: Acho que a diferença nos padrões de uso que tenho seria que, acho perfeitamente aceitável ignorar eventos, eles são ganchos / stubs, se você precisa saber sobre o evento, ouça-os, se você não se preocupa com o evento apenas ignore. É por isso que eu os uso para UI, tipo de estilo de evento Javascript / Browser. No entanto, quando eu tenho um delegado, espero REALMENTE esperar que alguém cuide da tarefa do delegado e lance uma exceção se não for tratada.

Robert Gould
fonte
Você poderia explicar melhor já que eu também uso pares na IU? Um bom exemplo seria suficiente ... obrigado
1

A diferença entre eventos e delegados é muito menor do que eu pensava. Acabei de postar um vídeo super curto no YouTube sobre o assunto: https://www.youtube.com/watch?v=el-kKK-7SBU

Espero que isto ajude!

Pontus Wittenmark
fonte
2
Bem-vindo ao Stack Overflow! Embora isso possa teoricamente responder à pergunta, seria preferível incluir as partes essenciais da resposta aqui e fornecer o link para referência.
GhostCat
1

Se usarmos apenas delegado no lugar de Evento, o assinante terá a oportunidade de clonar (), invocar () o próprio delegado, conforme mostrado abaixo na imagem. O que não está certo.

insira a descrição da imagem aqui

Essa é a principal diferença b / w evento e delegado. o assinante tem apenas um direito, ou seja, ouvir os eventos

A classe ConsoleLog está inscrevendo eventos de log via EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

A classe FileLog está inscrevendo eventos de log por meio de EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

A classe de operação está publicando eventos de log

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Narottam Goyal
fonte