C# Language
Eventos
Buscar..
Introducción
Un evento es una notificación de que algo ocurrió (como un clic del mouse) o, en algunos casos, está a punto de ocurrir (como un cambio de precio).
Las clases pueden definir eventos y sus instancias (objetos) pueden provocar estos eventos. Por ejemplo, un botón puede contener un evento de clic que se genera cuando un usuario lo ha hecho clic.
Los controladores de eventos son métodos que se llaman cuando se genera el evento correspondiente. Un formulario puede contener un controlador de eventos de clic para cada botón que contiene, por ejemplo.
Parámetros
Parámetro | Detalles |
---|---|
EventArgsT | El tipo que se deriva de EventArgs y contiene los parámetros del evento. |
Nombre del evento | El nombre del evento. |
Nombre del controlador | El nombre del controlador de eventos. |
SenderObject | El objeto que invoca el evento. |
EventosArgumentos | Una instancia del tipo EventArgsT que contiene los parámetros del evento. |
Observaciones
Al plantear un evento:
- Compruebe siempre si el delegado es
null
. Un delegado nulo significa que el evento no tiene suscriptores. Si se genera un evento sin suscriptores, se obtendrá unaNullReferenceException
.
- Copie el delegado (por ejemplo,
EventName
) en una variable local (por ejemplo,eventName
) antes de verificar si el evento es nulo /eventName
. Esto evita las condiciones de carrera en entornos de subprocesos múltiples:
Mal
if(Changed != null) // Changed has 1 subscriber at this point
// In another thread, that one subscriber decided to unsubscribe
Changed(this, args); // `Changed` is now null, `NullReferenceException` is thrown.
A la derecha
// Cache the "Changed" event as a local. If it is not null, then use
// the LOCAL variable (handler) to raise the event, NOT the event itself.
var handler = Changed;
if(handler != null)
handler(this, args);
- Use el operador condicional nulo (?.) Para elevar el método en lugar de verificar nulo al delegado en busca de suscriptores en una instrucción
if
:EventName?.Invoke(SenderObject, new EventArgsT());
- Cuando se usa Acción <> para declarar tipos de delegados, la firma del controlador de eventos / eventos anónimo debe ser la misma que el tipo de delegado anónimo declarado en la declaración de eventos.
Declarar y levantar eventos
Declarar un evento
Puedes declarar un evento en cualquier class
o struct
usando la siguiente sintaxis:
public class MyClass
{
// Declares the event for MyClass
public event EventHandler MyEvent;
// Raises the MyEvent event
public void RaiseEvent()
{
OnMyEvent();
}
}
Hay una sintaxis expandida para declarar eventos, donde se tiene una instancia privada del evento, y se define una instancia pública utilizando los accesores de add
y set
. La sintaxis es muy similar a las propiedades de C #. En todos los casos, se debe preferir la sintaxis demostrada anteriormente, ya que el compilador emite código para ayudar a garantizar que varios subprocesos puedan agregar y eliminar de forma segura los controladores de eventos al evento en su clase.
Elevando el evento
private void OnMyEvent()
{
EventName?.Invoke(this, EventArgs.Empty);
}
private void OnMyEvent()
{
// Use a local for EventName, because another thread can modify the
// public EventName between when we check it for null, and when we
// raise the event.
var eventName = EventName;
// If eventName == null, then it means there are no event-subscribers,
// and therefore, we cannot raise the event.
if(eventName != null)
eventName(this, EventArgs.Empty);
}
Tenga en cuenta que los eventos solo pueden ser provocados por el tipo declarante. Los clientes solo pueden suscribirse / darse de baja.
Para versiones de C # anteriores a 6.0, donde EventName?.Invoke
no es compatible, es una buena práctica asignar el evento a una variable temporal antes de la invocación, como se muestra en el ejemplo, que garantiza la seguridad de subprocesos en los casos en que varios subprocesos ejecutan el mismo código. Si no lo hace, puede provocar una NullReferenceException
en ciertos casos en los que varios subprocesos utilizan la misma instancia de objeto. En C # 6.0, el compilador emite un código similar al que se muestra en el ejemplo de código para C # 6.
Declaración de evento estándar
Declaración de evento:
public event EventHandler<EventArgsT> EventName;
Declaración del manejador de eventos:
public void HandlerName(object sender, EventArgsT args) { /* Handler logic */ }
Suscribiéndose al evento:
Dinamicamente:
EventName += HandlerName;
A través del Diseñador:
- Haga clic en el botón Eventos en la ventana de propiedades del control (Lightening bolt)
- Haga doble clic en el nombre del evento:
- Visual Studio generará el código del evento:
private void Form1_Load(object sender, EventArgs e)
{
}
Invocando el método:
EventName(SenderObject, EventArguments);
Declaración de manejador de eventos anónimos
Declaración de evento:
public event EventHandler<EventArgsType> EventName;
Declaración del manejador de eventos utilizando el operador lambda => y suscribiéndose al evento:
EventName += (obj, eventArgs) => { /* Handler logic */ };
Declaración del controlador de eventos mediante la sintaxis de métodos anónimos delegados :
EventName += delegate(object obj, EventArgsType eventArgs) { /* Handler Logic */ };
Declaración y suscripción de un controlador de eventos que no usa el parámetro del evento, por lo que puede usar la sintaxis anterior sin necesidad de especificar parámetros:
EventName += delegate { /* Handler Logic */ }
Invocando el evento:
EventName?.Invoke(SenderObject, EventArguments);
Declaración de evento no estándar
Los eventos pueden ser de cualquier tipo de delegado, no solo EventHandler
y EventHandler<T>
. Por ejemplo:
//Declaring an event
public event Action<Param1Type, Param2Type, ...> EventName;
Esto se usa de manera similar a los eventos de EventHandler
estándar:
//Adding a named event handler
public void HandlerName(Param1Type parameter1, Param2Type parameter2, ...) {
/* Handler logic */
}
EventName += HandlerName;
//Adding an anonymous event handler
EventName += (parameter1, parameter2, ...) => { /* Handler Logic */ };
//Invoking the event
EventName(parameter1, parameter2, ...);
Es posible declarar varios eventos del mismo tipo en una sola declaración, similar a los campos y las variables locales (aunque esto puede ser una mala idea):
public event EventHandler Event1, Event2, Event3;
Esto declara tres eventos separados ( Event1
, Event2
y Event3
) todos de tipo EventHandler
.
Nota: aunque algunos compiladores pueden aceptar esta sintaxis en las interfaces y en las clases, la especificación C # (v5.0 §13.2.3) proporciona una gramática para las interfaces que no lo permiten, por lo que su uso en las interfaces puede ser poco confiable con diferentes compiladores.
Creación de EventArgs personalizados que contienen datos adicionales
Los eventos personalizados generalmente necesitan argumentos de eventos personalizados que contengan información sobre el evento. Por ejemplo, MouseEventArgs
utilizado por eventos del mouse como MouseDown
o MouseUp
, contiene información sobre la Location
o los Buttons
que se utilizan para generar el evento.
Al crear nuevos eventos, para crear un evento personalizado arg:
- Cree una clase derivada de
EventArgs
y defina las propiedades para los datos necesarios. - Como convención, el nombre de la clase debe terminar con
EventArgs
.
Ejemplo
En el siguiente ejemplo, creamos un evento PriceChangingEventArgs
para la propiedad Price
de una clase. La clase de datos de evento contiene un CurrentPrice
y un NewPrice
. El evento aumenta cuando asigna un nuevo valor a la propiedad Price
y le informa al consumidor que el valor está cambiando y le informa sobre el precio actual y el nuevo precio:
PriceChangingEventArgs
public class PriceChangingEventArgs : EventArgs
{
public PriceChangingEventArgs(int currentPrice, int newPrice)
{
this.CurrentPrice = currentPrice;
this.NewPrice = newPrice;
}
public int CurrentPrice { get; private set; }
public int NewPrice { get; private set; }
}
Producto
public class Product
{
public event EventHandler<PriceChangingEventArgs> PriceChanging;
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = value;
}
}
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PriceChanging;
if (handler != null)
handler(this, e);
}
}
Puede mejorar el ejemplo permitiendo que el consumidor cambie el nuevo valor y luego el valor se usará para la propiedad. Para hacerlo es suficiente aplicar estos cambios en las clases.
Cambie la definición de NewPrice
para ser configurable:
public int NewPrice { get; set; }
Cambie la definición de Price
para usar e.NewPrice
como valor de propiedad, después de llamar a OnPriceChanging
:
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = e.NewPrice;
}
}
Creando evento cancelable
Un evento cancelable puede ser generado por una clase cuando está a punto de realizar una acción que puede cancelarse, como el evento FormClosing
de un Form
.
Para crear tal evento:
- Cree un nuevo evento
CancelEventArgs
enCancelEventArgs
y agregue propiedades adicionales para los datos del evento. - Cree un evento usando
EventHandler<T>
y use la nueva clase arg de evento de cancelación que creó.
Ejemplo
En el siguiente ejemplo, creamos un evento PriceChangingEventArgs
para la propiedad Price
de una clase. La clase de datos del evento contiene un Value
que le permite al consumidor conocer la nueva. El evento aumenta cuando asigna un nuevo valor a la propiedad Price
y le informa al consumidor que el valor está cambiando y le permite cancelar el evento. Si el consumidor cancela el evento, se utilizará el valor anterior para Price
:
PriceChangingEventArgs
public class PriceChangingEventArgs : CancelEventArgs
{
int value;
public int Value
{
get { return value; }
}
public PriceChangingEventArgs(int value)
{
this.value = value;
}
}
Producto
public class Product
{
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(value);
OnPriceChanging(e);
if (!e.Cancel)
price = value;
}
}
public event EventHandler<PriceChangingEventArgs> PropertyChanging;
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PropertyChanging;
if (handler != null)
PropertyChanging(this, e);
}
}
Propiedades del evento
Si una clase aumenta la cantidad de eventos, el costo de almacenamiento de un campo por delegado puede no ser aceptable. .NET Framework proporciona propiedades de eventos para estos casos. De esta manera, puede utilizar otra estructura de datos como EventHandlerList
para almacenar delegados de eventos:
public class SampleClass
{
// Define the delegate collection.
protected EventHandlerList eventDelegates = new EventHandlerList();
// Define a unique key for each event.
static readonly object someEventKey = new object();
// Define the SomeEvent event property.
public event EventHandler SomeEvent
{
add
{
// Add the input delegate to the collection.
eventDelegates.AddHandler(someEventKey, value);
}
remove
{
// Remove the input delegate from the collection.
eventDelegates.RemoveHandler(someEventKey, value);
}
}
// Raise the event with the delegate specified by someEventKey
protected void OnSomeEvent(EventArgs e)
{
var handler = (EventHandler)eventDelegates[someEventKey];
if (handler != null)
handler(this, e);
}
}
Este enfoque se usa ampliamente en marcos de GUI como WinForms, donde los controles pueden tener docenas e incluso cientos de eventos.
Tenga en cuenta que EventHandlerList
no es seguro para subprocesos, por lo que si espera que su clase se use desde varios subprocesos, deberá agregar declaraciones de bloqueo u otro mecanismo de sincronización (o usar un almacenamiento que proporcione seguridad de subprocesos).