Quais são as diferenças entre delegados e eventos?

Respostas:

283

Uma declaração de evento adiciona uma camada de abstração e proteção à instância delegada . Essa proteção impede que os clientes do delegado redefinam o delegado e sua lista de chamadas e apenas permite adicionar ou remover destinos da lista de chamadas.

mmcdole
fonte
44
Obviamente, essa camada de proteção também impede que "clientes" (código fora da classe / estrutura de definição) invoquem o delegado e obtenham de qualquer forma o objeto delegado "por trás" do evento.
Jeppe Stig Nielsen
7
Não é inteiramente verdade. Você pode declarar um evento sem uma instância de delegação de back-end. No c #, você pode implementar um evento explicitamente e usar uma estrutura de dados de back-end diferente de sua escolha.
Miguel Gamboa
3
@mmcdole você pode dar um exemplo para explicar a dele?
precisa saber é o seguinte
103

Para entender as diferenças, você pode ver estes 2 exemplos

Exemplo com delegados (neste caso, uma ação - é um tipo de delegado que não retorna um 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:

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

Esse 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, substituí os comportamentos anteriores apenas com um ausente +(usei em =vez de +=)

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

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

Sua classe Animal será alterada desta maneira:

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

    public string Operation {get; set;}
} 

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

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

chamar eventos

 Animal 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 (usando eventos, o compilador protege seus campos de acesso indesejado)
  2. Eventos não podem ser atribuídos diretamente. Nesse caso, não dará origem ao erro anterior que mostrei ao substituir o comportamento.
  3. Ninguém fora da sua turma pode promover o evento.
  4. 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 for proveniente de métodos estáticos.

Este exemplo, que usa EventHandler<ArgsSpecial>, também pode ser escrito usando EventHandler.

Consulte aqui para documentação sobre EventHandler

faby
fonte
7
Tudo parecia ótimo até eu me deparar com "Ninguém fora da sua turma pode promover o evento". O que isso significa? Ninguém pode ligar RaiseEventdesde que um método de chamada tenha acesso a uma instância do animalcódigo que usa o evento?
dance2die
11
Os eventos @Sung só podem ser gerados de dentro da classe, talvez eu não tenha sido claro explicando isso. Com eventos, você pode chamar a função que gera o evento (encapsulamento), mas ela só pode ser gerada de dentro da classe que o define. Deixe-me saber se eu não estiver claro.
faby
1
"Eventos não podem ser atribuídos diretamente." A menos que eu entenda errado, isso não é verdade. Aqui está um exemplo: gist.github.com/Chiel92/36bb3a2d2ac7dd511b96
Chiel ten Brinke
2
@ baby, você quer dizer que, embora o evento seja declarado público, eu ainda não posso fazer isso animal.Run(this, new ArgsSpecial("Run faster");?
18816 Pap
1
@ChieltenBrinke É claro que o evento pode ser atribuído a membros da classe ... mas não o contrário.
Jim Balter
94

Além das propriedades sintáticas e operacionais, há também uma diferença semântica.

Delegados são, conceitualmente, modelos de função; isto é, eles expressam um contrato ao qual uma função deve aderir para serem considerados do "tipo" do delegado.

Eventos representam ... bem, eventos. Eles pretendem alertar alguém quando algo acontece e, sim, eles seguem uma definição de delegado, mas não são a mesma coisa.

Mesmo se fossem exatamente a mesma coisa (sintaticamente e no código IL), ainda restará a diferença semântica. Em geral, prefiro ter dois nomes diferentes para dois conceitos diferentes, mesmo que sejam implementados da mesma maneira (o que não significa que eu gostaria de ter o mesmo código duas vezes).

Jorge Córdoba
fonte
8
Excelente descrição dos delegados.
Sampson
1
Então, poderíamos dizer que um evento é um tipo "especial" de delegado?
Pap
Não entendi seu ponto. Você pode usar um delegado para 'alertar alguém quando algo acontecer'. Talvez você não faça isso, mas pode e, portanto, não é uma propriedade inerente do evento.
steve
@Jorge Córdoba exemplo de delegado e delegado de eventos é o proprietário do jornal e os eventos (Assinar ou cancelar a inscrição) e algumas pessoas compram o jornal e outras não, o que significa que o proprietário do jornal não pode forçar todas as pessoas a comprarem o jornal. certo ou errado?
Rahul_Patil 15/04
37

Aqui está outro bom link para se referir. http://csharpindepth.com/Articles/Chapter2/Events.aspx

Resumidamente, a retirada do artigo - Eventos são encapsulamentos sobre delegados.

Citação do artigo:

Suponha que eventos não existissem como um conceito em C # / .NET. Como outra classe se inscreveria em um evento? Três opções:

  1. Uma variável pública delegada

  2. Uma variável delegada apoiada por uma propriedade

  3. Uma variável delegada com os métodos AddXXXHandler e RemoveXXXHandler

A opção 1 é claramente horrível, por todas as razões normais que abominamos variáveis ​​públicas.

A opção 2 é um pouco melhor, mas permite que os assinantes se substituam efetivamente - seria muito fácil escrever someInstance.MyEvent = eventHandler; que substituiria qualquer manipulador de eventos existente em vez de adicionar um novo. Além disso, você ainda precisa escrever as propriedades.

A opção 3 é basicamente o que os eventos fornecem, mas com uma convenção garantida (gerada pelo compilador e apoiada por sinalizadores extras na IL) e uma implementação "gratuita" se você estiver satisfeito com a semântica que os eventos semelhantes a campos fornecem. A inscrição e a desinscrição de eventos são encapsuladas sem permitir acesso arbitrário à lista de manipuladores de eventos, e os idiomas podem simplificar as coisas, fornecendo sintaxe para a declaração e a assinatura.

vibhu
fonte
Explicação agradável e concisa. Thanx
Pap
Isso é mais uma preocupação teórica do que qualquer coisa, mas FWIW sempre achei que o argumento "A opção 1 é ruim porque não gostamos de variáveis ​​públicas" poderia usar um pouco mais de esclarecimento. Se ele está dizendo isso porque é "má prática de POO", tecnicamente uma public Delegatevariável estaria expondo "dados", mas, pelo que sei, a POO nunca mencionou conceitos como a Delegate(não é um "objeto" nem uma "mensagem") e o .NET mal trata os delegados como dados de qualquer maneira.
JRH
Embora eu também gostaria de dar conselhos mais práticos, se você estiver em uma situação em que deseja garantir que haja apenas um manipulador, criar seus próprios AddXXXHandlermétodos com uma private Delegatevariável pode ser uma boa opção. Nesse caso, você pode verificar se um manipulador já está definido e reagir adequadamente. Essa também pode ser uma boa configuração, se você precisar que o objeto que está segurando Delegateseja capaz de limpar todos os manipuladores ( eventnão fornece nenhuma maneira de fazer isso).
Jrh 29/08/19
7

NOTA: Se você tiver acesso ao C # 5.0 Unleashed , leia as "Limitações no uso simples de delegados" no capítulo 18, intitulado "Eventos" para entender melhor as diferenças entre os dois.


Sempre me ajuda a ter um exemplo simples e concreto. Então aqui está um para a comunidade. Primeiro, mostro como você pode usar delegados sozinhos para fazer o que os Eventos fazem por nós. Então eu mostro como a mesma solução funcionaria com uma instância de EventHandler. E então eu explico por que não queremos fazer o que explico no primeiro exemplo. Este post foi inspirado em um artigo de John Skeet.

Exemplo 1: Usando delegado público

Suponha que eu tenha um aplicativo WinForms com uma única caixa suspensa. O menu suspenso está vinculado a um List<Person>. Onde Person tem propriedades de Id, Name, NickName, HairColor. No formulário principal, há um controle de usuário personalizado que mostra as propriedades dessa pessoa. Quando alguém seleciona uma pessoa no menu suspenso, os rótulos na atualização de controle do usuário mostram as propriedades da pessoa selecionada.

insira a descrição da imagem aqui

Aqui está como isso funciona. Temos três arquivos que nos ajudam a montar isso:

  • Mediator.cs - classe estática mantém os delegados
  • Form1.cs - formulário principal
  • DetailView.cs - o controle do usuário mostra todos os detalhes

Aqui está o código relevante para cada uma das classes:

class Mediator
{
    public delegate void PersonChangedDelegate(Person p); //delegate type definition
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
    {
        if (PersonChangedDel != null)
        {
            PersonChangedDel(p);
        }
    }
}

Aqui está o nosso controle de usuário:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.PersonChangedDel += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(Person p)
    {
        BindData(p);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

Finalmente, temos o seguinte código em nosso Form1.cs. Aqui estamos chamando OnPersonChanged, que chama qualquer código inscrito no delegado.

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

Está bem. Então é assim que você faria isso funcionar sem usar eventos e apenas usando delegados . Acabamos de colocar um representante público em uma classe - você pode torná-lo estático ou único, ou o que for. Ótimo.

MAS, MAS, MAS, nós não queremos fazer o que acabei de descrever acima. Porque os campos públicos são ruins por muitos, muitos motivos. Então, quais são nossas opções? Como John Skeet descreve, aqui estão nossas opções:

  1. Uma variável pública delegada (foi o que acabamos de fazer acima. Não faça isso. Acabei de lhe dizer por que é ruim)
  2. Coloque o delegado em uma propriedade com um get / set (o problema aqui é que os assinantes podem se substituir - para que possamos assinar vários métodos para o delegado e, em seguida, acidentalmente digamos PersonChangedDel = null, eliminando todas as outras assinaturas. outro problema que permanece aqui é que, como os usuários têm acesso ao delegado, eles podem invocar os destinos na lista de chamadas - não queremos que usuários externos tenham acesso a quando criar nossos eventos.
  3. Uma variável delegada com os métodos AddXXXHandler e RemoveXXXHandler

Esta terceira opção é essencialmente o que um evento nos oferece. Quando declaramos um EventHandler, ele nos dá acesso a um delegado - não publicamente, não como uma propriedade, mas como isso chamamos de um evento que acabou de adicionar / remover acessadores.

Vamos ver como é o mesmo programa, mas agora usando um Evento em vez do representante público (eu também mudei nosso Mediador para um singleton):

Exemplo 2: Com EventHandler em vez de um representante público

Mediador:

class Mediator
{

    private static readonly Mediator _Instance = new Mediator();

    private Mediator() { }

    public static Mediator GetInstance()
    {
        return _Instance;
    }

    public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.

    public void OnPersonChanged(object sender, Person p)
    {
        var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
        if (personChangedDelegate != null)
        {
            personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
        }
    }
}

Observe que se você F12 no EventHandler, ele mostrará que a definição é apenas um delegado genérico com o objeto "remetente" extra:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

O Controle do Usuário:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
    {
        BindData(e.Person);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

Finalmente, aqui está o código Form1.cs:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
        Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}

Como o EventHandler deseja e EventArgs como parâmetro, eu criei essa classe com apenas uma propriedade:

class PersonChangedEventArgs
{
    public Person Person { get; set; }
}

Espero que isso mostre um pouco sobre por que temos eventos e como eles são diferentes - mas funcionalmente iguais - como delegados.

Trevor
fonte
Embora apreciei todo o bom trabalho deste post e gostei de ler a maioria, ainda sinto que um problema não foi resolvido - The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events. Na versão mais recente do Mediator, você ainda pode chamar o OnPersonChangesempre que tiver uma referência ao singleton. Talvez você deva mencionar que a Mediatorabordagem não impede esse comportamento específico e está mais próxima de um barramento de eventos.
Ivaylo Slavov
6

Você também pode usar eventos em declarações de interface, não para delegados.

Paul Hill
fonte
2
A interface do @surfen pode conter eventos, mas não delegados.
Alexandr Nikitin
1
O que exatamente você quer dizer? Você pode ter Action a { get; set; }dentro de uma definição de interface.
Chiel ten Brinke
6

Que grande mal-entendido entre eventos e delegados !!! Um delegado especifica um TYPE (como um class, ou interfacefaz), enquanto um evento é apenas um tipo de MEMBER (como campos, propriedades, etc.). E, como qualquer outro tipo de membro, um evento também tem um tipo. No entanto, no caso de um evento, o tipo do evento deve ser especificado por um delegado. Por exemplo, você NÃO PODE declarar um evento de um tipo definido por uma interface.

Concluindo, podemos fazer a seguinte observação: o tipo de um evento DEVE ser definido por um delegado . Esta é a principal relação entre um evento e um delegado e é descrita na seção II.18. Definindo eventos das partições I a VI do ECMA-335 (CLI) :

No uso típico, o TypeSpec (se presente) identifica um delegado cuja assinatura corresponde aos argumentos transmitidos ao método de disparo do evento.

No entanto, esse fato NÃO implica que um evento use um campo de delegado de apoio . Na verdade, um evento pode usar um campo de apoio de qualquer tipo diferente de estrutura de dados de sua escolha. Se você implementar um evento explicitamente em C #, poderá escolher a maneira como armazena os manipuladores de eventos (observe que os manipuladores de eventos são instâncias do tipo do evento , que por sua vez é obrigatoriamente um tipo de delegado --- da Observação anterior ) Mas, você pode armazenar os manipuladores de eventos (que são instâncias de delegação) em uma estrutura de dados como uma Listou outra Dictionaryou qualquer outra coisa, ou mesmo em um campo de delegado de apoio. Mas não esqueça que NÃO é obrigatório que você use um campo de delegado.

Miguel Gamboa
fonte
4

Um evento no .net é uma combinação designada de um método Add e um método Remove, os quais esperam algum tipo específico de delegado. O C # e o vb.net podem gerar automaticamente código para os métodos add e remove, que definirão um delegado para armazenar as inscrições de eventos e adicionar / remover o delegado passado no / para o delegado da assinatura. O VB.net também gerará automaticamente o código (com a instrução RaiseEvent) para invocar a lista de assinaturas se e somente se não estiver vazia; por alguma razão, o C # não gera o último.

Observe que, embora seja comum gerenciar assinaturas de eventos usando um representante multicast, esse não é o único meio de fazer isso. De uma perspectiva pública, um possível assinante de evento precisa saber como informar um objeto que deseja receber eventos, mas não precisa saber qual mecanismo o editor usará para gerar os eventos. Observe também que, enquanto quem definiu a estrutura de dados do evento em .net aparentemente pensou que deveria haver um meio público de aumentá-los, nem o C # nem o vb.net fazem uso desse recurso.

supercat
fonte
3

Para definir um evento de maneira simples:

Evento é uma REFERÊNCIA a um delegado com duas restrições

  1. Não pode ser invocado diretamente
  2. Não é possível atribuir valores diretamente (por exemplo, eventObj = delegateMethod)

Acima de dois, estão os pontos fracos dos delegados e são abordados no evento. Exemplo de código completo para mostrar a diferença no violinista está aqui https://dotnetfiddle.net/5iR3fB .

Alterne o comentário entre Evento e Delegado e o código do cliente que chama / atribui valores para delegar para entender a diferença

Aqui está o código embutido.

 /*
This is working program in Visual Studio.  It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
        Event is an delegate reference with two restrictions for increased protection

            1. Cannot be invoked directly
            2. Cannot assign value to delegate reference directly

Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/

public class RoomTemperatureController
{
    private int _roomTemperature = 25;//Default/Starting room Temperature
    private bool _isAirConditionTurnedOn = false;//Default AC is Off
    private bool _isHeatTurnedOn = false;//Default Heat is Off
    private bool _tempSimulator = false;
    public  delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
    // public  OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
    public  event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 

    public RoomTemperatureController()
    {
        WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
    }
    private void InternalRoomTemperatuerHandler(int roomTemp)
    {
        System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
    }

    //User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
    public bool TurnRoomTeperatureSimulator
    {
        set
        {
            _tempSimulator = value;
            if (value)
            {
                SimulateRoomTemperature(); //Turn on Simulator              
            }
        }
        get { return _tempSimulator; }
    }
    public void TurnAirCondition(bool val)
    {
        _isAirConditionTurnedOn = val;
        _isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }
    public void TurnHeat(bool val)
    {
        _isHeatTurnedOn = val;
        _isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }

    public async void SimulateRoomTemperature()
    {
        while (_tempSimulator)
        {
            if (_isAirConditionTurnedOn)
                _roomTemperature--;//Decrease Room Temperature if AC is turned On
            if (_isHeatTurnedOn)
                _roomTemperature++;//Decrease Room Temperature if AC is turned On
            System.Console.WriteLine("Temperature :" + _roomTemperature);
            if (WhenRoomTemperatureChange != null)
                WhenRoomTemperatureChange(_roomTemperature);
            System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
        }
    }

}

public class MySweetHome
{
    RoomTemperatureController roomController = null;
    public MySweetHome()
    {
        roomController = new RoomTemperatureController();
        roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
        //roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
        //roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
        roomController.SimulateRoomTemperature();
        System.Threading.Thread.Sleep(5000);
        roomController.TurnAirCondition (true);
        roomController.TurnRoomTeperatureSimulator = true;

    }
    public void TurnHeatOrACBasedOnTemp(int temp)
    {
        if (temp >= 30)
            roomController.TurnAirCondition(true);
        if (temp <= 15)
            roomController.TurnHeat(true);

    }
    public static void Main(string []args)
    {
        MySweetHome home = new MySweetHome();
    }


}
Venkatesh Muniyandi
fonte
2

Delegado é um ponteiro de função com segurança de tipo. Evento é uma implementação do padrão de design do publicador-assinante usando delegate.

Weidong Shen
fonte
0

Se você marcar Intermediate Language, você saberá que o compilador .net converte delegate para uma classe selada na IL com algumas funções internas, como invoke, beginInvoke, endInvoke e delegate class herdadas de outra classe, talvez chamada "SystemMulticast". Eu acho que Event é uma classe filho de Delegado com algumas propriedades adicionais.

A diferença entre instância do evento e delegado é que você não pode executar o evento fora da declaração. Se você declarar um evento na classe A, poderá executá-lo apenas na classe A. Se você declarar um delegado na Classe A, poderá usá-lo em qualquer lugar. Eu acho que essa é a principal diferença entre eles

Shawn L
fonte