Quais são as diferenças entre delegados e um evento? Ambos não possuem referências a funções que podem ser executadas?
Quais são as diferenças entre delegados e um evento? Ambos não possuem referências a funções que podem ser executadas?
Respostas:
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.
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 Animal
classe pode aumentar RaiseEvent
apenas chamando animal.RaiseEvent()
.
Para evitar esses pontos fracos, você pode usar events
em 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:
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
RaiseEvent
desde que um método de chamada tenha acesso a uma instância do animal
código que usa o evento?
animal.Run(this, new ArgsSpecial("Run faster");
?
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).
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:
Uma variável pública delegada
Uma variável delegada apoiada por uma propriedade
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.
public Delegate
variá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.
AddXXXHandler
métodos com uma private Delegate
variá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 Delegate
seja capaz de limpar todos os manipuladores ( event
não fornece nenhuma maneira de fazer isso).
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.
Aqui está como isso funciona. Temos três arquivos que nos ajudam a montar isso:
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:
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.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.
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 OnPersonChange
sempre que tiver uma referência ao singleton. Talvez você deva mencionar que a Mediator
abordagem não impede esse comportamento específico e está mais próxima de um barramento de eventos.
Você também pode usar eventos em declarações de interface, não para delegados.
Action a { get; set; }
dentro de uma definição de interface.
Que grande mal-entendido entre eventos e delegados !!! Um delegado especifica um TYPE (como um class
, ou interface
faz), 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 List
ou outra Dictionary
ou 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.
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.
Para definir um evento de maneira simples:
Evento é uma REFERÊNCIA a um delegado com duas restrições
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();
}
}
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.
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