Pergunta

Quais são as diferenças entre delegados e eventos?Não possuem referências a funções que podem ser executadas?

Foi útil?

Solução

Um Eventos declaração adiciona uma camada de abstração e de proteção na delegado instância.Esta proteção impede que os clientes do delegado de repor o delegado e sua lista de invocação e só permite a adição ou remoção de metas a partir da lista de invocação.

Outras dicas

Além sintática e propriedades operacionais, há também uma semântico diferença.

Os delegados são, conceitualmente, a função de modelos;isto é, eles expressam um contrato de uma função deve cumprir para ser considerado o "tipo" do delegado.

Eventos representam ...bem, eventos.Eles têm a intenção de alertar alguém quando algo acontece e sim, eles aderem a um delegado definição, mas eles não são a mesma coisa.

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

Para entender as diferenças que você pode olhar para este 2 exemplos

Exemplo com os Delegados (neste caso, uma Ação - que é uma espécie 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 como isto:

Animal 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, eu tenho substituído o anterior comportamentos apenas com um ausente + (Eu usei = em vez de +=)

Outro ponto fraco é que cada classe que usa o seu Animal classe pode aumentar RaiseEvent chamando de animal.RaiseEvent().

Para evitar estes pontos fracos que você pode usar events em c#.

Seu Animal de classe vai mudar desta forma:

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

para eventos de chamada de

 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 um bem público, mas um público de campo (usando eventos, o compilador protege os campos contra acesso indesejado)
  2. Eventos não podem ser atribuídos diretamente.Neste caso, não dar lugar para o erro anterior que eu mostrei com a substituir o comportamento.
  3. Ninguém fora de sua classe pode aumentar o evento.
  4. Os eventos podem ser incluídos em uma declaração de interface, enquanto que um campo não pode

Notas:

EventHandler é declarado como o seguinte delegado:

public delegate void EventHandler (object sender, EventArgs e)

é preciso um remetente (de tipo de Objecto) e argumentos do evento.O remetente é nulo, se se trata de métodos estáticos.

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

Consulte aqui para documentação sobre EventHandler

Aqui é outro bom link para consultar.http://csharpindepth.com/Articles/Chapter2/Events.aspx

Por alguns instantes, a levar de distância do artigo - os Eventos são de encapsulamento mais de delegados.

Citação de artigo:

Suponha que os eventos não existia como um conceito em C#/.LÍQUIDA.Como seria de outra classe se inscrever em um evento?Três opções:

  1. Um público de delegado variável

  2. Um delegado variável, apoiado por uma propriedade

  3. Um delegado variável com AddXXXHandler e RemoveXXXHandler métodos

A opção 1 é, claramente, horrível, por todos os motivos abominamos variáveis públicas.

A opção 2 é ligeiramente melhor, mas permite que os assinantes para efetivamente substituir uns aos outros - seria muito fácil escrever someInstance.MyEvent = eventHandler;que iria substituir a existente manipuladores de eventos em vez de adicionar um novo.Além disso, você ainda precisa escrever as propriedades.

A opção 3 é basicamente o que os eventos de dar a você, mas com uma garantia de convenção (gerado pelo compilador e apoiado por extra sinalizadores no IL) e a "livre" implementação se você está feliz com a semântica de campo, como eventos, dar-lhe.Subscrição e anulação da subscrição de eventos é encapsulado, sem permitir que o acesso arbitrário a lista de manipuladores de eventos, e línguas, pode tornar as coisas mais simples, fornecendo a sintaxe para ambos declaração e assinatura.

NOTA:Se você tiver acesso a C# 5.0 Unleashed, leia as "Limitações na Simples Utilização de Delegados", no Capítulo 18, intitulado "Eventos" para entender melhor as diferenças entre os dois.


Ele sempre me ajuda a ter um simples exemplo concreto.Então aqui está um para a comunidade.Primeiro vou mostrar como você pode usar os delegados sozinho para fazer o que os Eventos por nós.Então eu mostrar como a mesma solução seria trabalhar com uma instância de EventHandler.E então me explique por que nós NÃO queremos fazer o que eu explico no primeiro exemplo.Este post foi inspirado pela um artigo por João Skeet.

Exemplo 1:Usando público delegado

Suponha que eu tenha uma aplicação WinForms com uma única caixa drop-down.A lista drop-down é vinculada a um List<Person>.Onde a Pessoa tem propriedades de Id, Nome, Apelido, HairColor.No formulário principal é um controlo de utilizador personalizadas que mostra as propriedades da pessoa.Quando alguém escolhe uma pessoa na lista drop-down os rótulos no controle de usuário de atualização para mostrar as propriedades da pessoa selecionada.

enter image description here

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

  • Mediador.cs -- classe estática que contém os delegados
  • Form1.cs -- formulário principal
  • DetailView.cs -- controlo de utilizador mostra todos os detalhes

Aqui está o código 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 é o nosso controlo de utilizador:

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 de OnPersonChanged, que chama qualquer código de inscritos para o 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`.
}

Ok.Então, isso é como você gostaria de obter esse trabalho sem a utilização de eventos e usando apenas os delegados.É só colocar um público de delegado em uma classe -- você pode torná-lo estático ou um singleton, ou seja o que for.Grande.

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

  1. Um público de delegado variável (isto é o que fizemos acima.não faça isso.eu disse acima, por que é ruim)
  2. Coloque o delegado em uma propriedade com get/set (problema aqui é que os assinantes podem substituir uns aos outros-para que possam assinar um monte de métodos para o delegado e, em seguida, poderíamos dizer acidentalmente PersonChangedDel = null, exterminando todas as outras assinaturas.O outro problema que permanece aqui é que, desde que os utilizadores tenham acesso ao delegado, eles podem invocar os alvos na lista de invocação -- não queremos que usuários externos tenham acesso para quando levantar nossos eventos.
  3. Um delegado variável com AddXXXHandler e RemoveXXXHandler métodos

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

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

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

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, ela irá mostrar-lhe a definição é apenas um genérico-ified delegado com o extra "remetente" objeto:

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

O Controle De 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 Form1.cs código:

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

Porque o EventHandler quer e EventArgs como um parâmetro, eu criei esta classe com apenas um único imóvel em que:

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

Espero que mostra um pouco sobre o porquê temos eventos e como eles são diferentes -- mas funcionalmente iguais-delegados.

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

O que é um grande mal-entendido entre os eventos e os delegados!!!Um delegado especifica um TIPO (como um class, ou um interface faz), enquanto que um evento é apenas um tipo de MEMBRO (tais como campos, propriedades, etc.).E, assim como qualquer outro tipo de membro de um evento também tem um tipo.Contudo, no caso de um evento, o tipo de evento deve ser especificada por um delegado.Por exemplo, você NÃO pode declarar um evento de um tipo definido por uma interface.

Concluindo, podemos fazer o 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 é descrito na seção II.18 a Definição de eventos de ECMA-335 (CLI) Partições de I a VI:

No uso típico, o TypeSpec (se presente) identifica um delegado cuja assinatura coincide com os argumentos passados para o evento do método fire.

No entanto, este facto NÃO implica que um evento usa um backup de delegado de campo.Na verdade, um evento pode utilizar um campo de apoio de qualquer outro tipo de estrutura de dados de sua escolha.Se você implementar um evento explicitamente em C#, você está livre para escolher a maneira de armazenar o manipuladores de eventos (note que manipuladores de eventos são instâncias da tipo do evento, que por sua vez é obrigatoriamente uma tipo de delegado---anterior Observação).Mas, você pode armazenar esses manipuladores de eventos (que são delegado instâncias) em uma estrutura de dados, tais como um List ou um Dictionary ou qualquer outra pessoa, ou até mesmo em um backup de delegado de campo.Mas não se esqueça que NÃO é obrigatório que você use um representante de campo.

Um evento .net é uma designado combinação de um método de Adicionar e Remover um método, do que esperar algum tipo particular de delegado.C# e vb.net pode gerar automaticamente o código para os métodos add e remove, que irá definir um delegado para realizar o evento, inscrições, e adicionar/remover passado na delegagte de/para a assinatura do delegado.VB.net também auto-gerar código (com a instrução RaiseEvent) para chamar a lista de subscrição, se e somente se a é não-vazio;por algum motivo, o C# não gerar o último.

Observe que, embora seja comum a gerir subscrições de eventos usando um delegado multicast, que não é o único meio de fazê-lo.A partir de um ponto de vista público, seria o assinante do evento precisa saber como permitir que um objeto sabe que ele quer para receber eventos, mas ele não precisa saber que mecanismo o editor irá usar para elevar os eventos.Observe também que, enquanto quem definiu o evento de estrutura de dados .net, aparentemente, o pensamento não deve ser um meio público de criá-los, C# nem vb.net faz uso desse recurso.

Para definir sobre o evento de forma simples:

O evento é uma REFERÊNCIA para um delegado com duas restrições

  1. Não pode ser chamado diretamente
  2. Não podem ser atribuídos valores diretamente (e.g eventObj = delegateMethod)

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

Alternar o comentário entre o Evento e o Delegado e o código de cliente que invoca/atribuir valores para delegar para entender a diferença

Aqui está o código inline.

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


}

Covariance e Contravariance oferecem maior flexibilidade para o delegado objetos.Por outro lado, um evento não tem nenhum desses conceitos.

  • Covariance permite que você atribua um método para o delegado, onde o tipo de retorno do método é uma classe que é derivada da classe que especifica o tipo de retorno do delegado.
  • Contravariance permite que você atribua um método para o delegado, onde o tipo de parâmetro do método é uma base de classe da classe que é especificado como o parâmetro do delegado.

Delegado é um tipo de ponteiro de função de segurança.O evento é uma implementação do editor-assinante padrão de design usando o delegado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top