2011-04-11 19 views
11

Estoy bastante convencido de que esto no es posible, pero voy a preguntar de todos modos.Suscripción de evento de un solo disparo

Con el fin de realizar una suscripción de un solo tiro a eventos, con frecuencia me encuentro con este patrón (auto-inventado):

EventHandler handler=null; 
handler = (sender, e) => 
{ 
    SomeEvent -= handler; 
    Initialize(); 
}; 
SomeEvent += handler; 

Es un buen montón de caldera de la placa, y también hace que ReSharper Whinge sobre cierres modificados. ¿Hay alguna manera de convertir este patrón en un método de extensión o similar? Una mejor manera de hacerlo?

Idealmente, me gustaría algo así como:

SomeEvent.OneShot(handler) 
+0

No veo ninguna razón por la cual no pueda poner el código repetitivo en un método de extensión. ¿O no tengo la pregunta? – Achim

+0

¿Cómo se aprueba el evento para cancelar la suscripción? ¿Tiene un tipo de base común? No es un delegado un tipo de valor (es decir, si pasa el evento de alguna manera, se trata de una copia) – spender

+0

@spender: Todos los delegados son tipos de referencia. –

Respuesta

4

No es muy fácil de refactorizar a un método de extensión, debido a que la única manera que puede referirse a un evento en C# es mediante la suscripción (+=) a o darse de baja (-=) de ella (a menos que esté declarado en la clase actual).

Puede utilizar el mismo enfoque que en las extensiones reactivas: Observable.FromEvent toma dos delegados para suscribirse al evento y anular su suscripción. Entonces usted podría hacer algo como eso:

public static class EventHelper 
{ 
    public static void SubscribeOneShot(
     Action<EventHandler> subscribe, 
     Action<EventHandler> unsubscribe, 
     EventHandler handler) 
    { 
     EventHandler actualHandler = null; 
     actualHandler = (sender, e) => 
     { 
      unsubscribe(actualHandler); 
      handler(sender, e); 
     }; 
     subscribe(actualHandler); 
    } 
} 

... 

Foo f = new Foo(); 
EventHelper.SubscribeOneShot(
    handler => f.Bar += handler, 
    handler => f.Bar -= handler, 
    (sender, e) => { /* whatever */ }); 
+0

Buen intento, pero con toda la delegación de suscripción/desuscripción es casi la misma cantidad de repetición, pero usando una interfaz desconocida que no es obvia en la primera lectura.Tampoco se aplicará la suscripción/desuscripción en los delegados proporcionados por el usuario, lo que significa que sería fácil de romper y me preocupa el nombre del método. Gracias, pero me quedaré con el mío :) – spender

+0

Diría que este es un paso en la dirección correcta. Después de todo, la intención del código está claramente establecida por el nombre del método y hay un poco menos de código para escribir. Sin duda es un comienzo. –

+1

+1, no puede abstraer 'f.Bar + =/- = handler' lejos de la persona que llama de todos modos, y la suscripción/eliminación realizada en el método de extensión en lugar del controlador puede considerarse una característica, no un error:) –

1

El siguiente código funciona para mí. No es perfecto tener que especificar el evento a través de una cadena, pero no tengo pegamento para resolverlo. Supongo que no es posible en la versión actual de C#.

using System; 
using System.Reflection; 

namespace TestProject 
{ 
    public delegate void MyEventHandler(object sender, EventArgs e); 

    public class MyClass 
    { 
     public event MyEventHandler MyEvent; 

     public void TriggerMyEvent() 
     { 
      if (MyEvent != null) 
      { 
       MyEvent(null, null); 
      } 
      else 
      { 
       Console.WriteLine("No event handler registered."); 
      } 
     } 
    } 

    public static class MyExt 
    { 
     public static void OneShot<TA>(this TA instance, string eventName, MyEventHandler handler) 
     { 
      EventInfo i = typeof (TA).GetEvent(eventName); 
      MyEventHandler newHandler = null; 
      newHandler = (sender, e) => 
          { 
           handler(sender, e); 
           i.RemoveEventHandler(instance, newHandler); 
          }; 
      i.AddEventHandler(instance, newHandler); 
     } 
    } 

    public class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass c = new MyClass(); 
      c.OneShot("MyEvent",(sender,e) => Console.WriteLine("Handler executed.")); 
      c.TriggerMyEvent(); 
      c.TriggerMyEvent(); 
     } 
    } 
} 
+0

Acabo de enterarme de que un acceso a eventos personalizado también puede ser una opción. Pero eso depende de tus preferencias y requisitos. – Achim

1

se recomienda usar un evento "a medida" para que tenga acceso a la lista de invocación, y luego provocar el evento mediante el uso de Interlocked.Exchange para leer y borrar la lista de invocación al mismo tiempo. Si lo desea, la suscripción/desuscripción/subida del evento se podría realizar de forma segura para hilos utilizando una pila de listas enlazadas; cuando se levanta el evento, el código podría, después del Interlocked.Exchange, invertir el orden de los elementos de la pila. Para el método de cancelación de suscripción, probablemente sugeriría simplemente establecer una bandera dentro del elemento de lista de invocación. En teoría, esto podría causar una pérdida de memoria si los eventos se suscribieran y cancelaran repetidamente sin que se produjera el evento, pero haría que el método de suscripción a subprocesos fuera muy seguro. Si uno quisiera evitar una pérdida de memoria, uno podría contar cuántos eventos no suscritos aún están en la lista; si hay demasiados eventos no suscritos en la lista cuando se intenta agregar uno nuevo, el método agregar podría pasar por la lista y eliminarlos. Aún funcionable en código seguro para subprocesos sin bloqueo, pero más complicado.

Cuestiones relacionadas