2010-05-26 10 views
6

Esto es lo que quiero decir. Supongamos que estoy trabajando con una API que expone eventos, pero estos eventos no siguen la firma estándar EventHandler o EventHandler<TEventArgs>. Un evento podría tener este aspecto, por ejemplo:¿Cómo puedo obtener un IObservable <T> en Rx de un evento "no estándar"?

Public Event Update(ByVal sender As BaseSubscription, ByVal e As BaseEvent) 

Ahora, por lo general, si quiero tener una IObservable<TEventArgs> de un evento, sólo puede hacer esto:

Dim updates = Observable.FromEvent(Of UpdateEventArgs)(_ 
    target:=updateSource, _ 
    eventName:="Update" _ 
) 

pero esto no significa trabajo, porque el evento Update no es un EventHandler<UpdateEventArgs> - de hecho, hay es no UpdateEventArgs - básicamente es solo lo suyo.

Obviamente, podría definir mi propios procedentes clase de EventArgs (es decir, UpdateEventArgs), escribir otra clase para envolver el objeto que proporciona el evento Update, dar la clase contenedora su propio evento Update que es una EventHandler<UpdateEventArgs>, y obtenga IObservable<UpdateEventArgs> de que. Pero esa es una cantidad molesta de trabajo.

¿Hay alguna forma de crear un IObservable<[something]> a partir de un evento "no estándar" como este, o no tengo suerte?


ACTUALIZACIÓN: A partir de la respuesta de Jon Skeet, me dio un codazo en la dirección de la siguiente sobrecarga del Observable.FromEvent:

Function FromEvent(Of TDelegate, TEventArgs As EventArgs)(_ 
    conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _ 
    addHandler As Action(Of TDelegate), _ 
    removeHandler As Action(Of TDelegate) _ 
) As IObservable(Of IEvent(Of TEventArgs)) 

tengo que admitir, sin embargo, estoy teniendo problemas para envolver mi cabeza alrededor de esa parte Func(Of EventHandler(Of TEventArgs), TDelegate). Me parece al revés (?). Obviamente, hay algo que me falta ...

De todos modos, en caso de que ayuda, yo creo esto es lo que el código C# equivalentes se vería así (Voy a ser honesto: No estoy seguro . a pesar de que este generalmente prefiero C# yo, este código es la obra de uno de mis colegas, que escribe principalmente en VB.NET, y VB.NET permite múltiples sintaxis para los eventos que declaran):

// notice: not an EventHandler<TEventArgs> 
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e); 

// not 100% sure why he did it this way 
public event UpdateEventHandler Update; 

el La parte difícil aquí es que parece que algún tipo de clase derivado de EventArgs es necesario, sin importar qué. En la API con la que estoy trabajando, no hay tal clase. Entonces, como mínimo, tendré que escribir uno. Pero eso debería ser bastante trivial (básicamente una propiedad: BaseEvent).

Al final, estoy suponiendo que el código necesario para esta sobrecarga sería algo como esto en C#:

var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
    // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>) 
    handler => (sender, e) => handler(sender, new UpdateEventArgs(e)), 
    // addHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update += handler, 
    // removeHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update -= handler 
); 

En primer lugar: qué me he entendido bien? En segundo lugar: ¿tengo razón al decir que al usar VB 9, realmente no hay forma de lograr lo anterior sin escribir mis propios métodos?

Casi me parece que estoy abordando este problema desde el ángulo totalmente equivocado para empezar. Pero realmente no estoy seguro

+0

Su ejemplo de C# era correcto, la forma de hacerlo en VB es la misma, pero la sintaxis es un poco más desordenada. Vea el ejemplo que publiqué a continuación para referencia futura. –

Respuesta

1

Usted puede poder utilizar esta firma:

Public Shared Function FromEvent(Of TDelegate, TEventArgs As EventArgs) (_ 
    conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _ 
    addHandler As Action(Of TDelegate), _ 
    removeHandler As Action(Of TDelegate) _ 
) As IObservable(Of IEvent(Of TEventArgs)) 

Aquí, TDelegate sería el tipo de delegado del evento (que no puedo decir inmediatamente de su declaración - declaraciones de C# de eventos Don' Parece bastante así, y me temo que no sé suficiente VB para descifrarlo, pero estoy seguro de que hay un tipo de delegado allí en algún lugar). TEventArgs sería un tipo para el argumento del evento (BaseEvent debería hacerlo aquí, creo). Debería proporcionar un convertidor de EventHandler(Of BaseEvent) a su tipo de delegado; esto probablemente sería una expresión lambda para llamar al manejador de eventos dado con los argumentos pasados. Las acciones de agregar y eliminar serían el código de suscripción a un evento normal, pero se expresarían como delegados.

Desafortunadamente, mi VB no es lo suficientemente bueno para expresar todo esto con nitidez o, de hecho, saber cuánto está disponible fácilmente en VB 9 o 10. Sé cómo se vería todo en C# ... si pudieras dar un ejemplo corto pero completo en C# que me dejó para completar el bit de suscripción, sin duda podría hacerlo ...

+0

Esto definitivamente parece una opción prometedora. He actualizado mi pregunta con lo que * creo * es el equivalente C# del código VB.NET que originalmente publiqué. Si tienes la oportunidad de leer mi actualización, ¿te parece correcta mi versión de C# del código hipotético que necesitaría escribir? Sé que dijiste que no estás muy familiarizado con VB, así que supongo que estoy solo cuando se trata de resolver esa parte ... –

3

¿Tal vez podría simplemente agregar su propia implementación para la firma personalizada de eventos?

public interface ICustomEvent<TSource, TArgs> 
{ 
    public TSource Source { get; } 
    public TArgs EventArgs { get; } 
} 

public interface CustomEvent<TSource, TArgs> : ICustomEvent<TSource, TArgs> 
{ 
    public TSource Source { get; set; } 
    public TArgs EventArgs { get; set; } 
} 

public static class ObservableEx 
{ 
    public static IObservable<ICustomEvent<TSource, TArgs>> FromCustomEvent(
     Action<Action<TSource, TArgs>> addHandler, 
     Action<Action<TSource, TArgs>> removeHandler) 
    { 
     return Observable.CreateWithDisposable(observer => 
      { 
       Action<TSource, TArgs> eventHandler = (s,a) => 
        observer.OnNext(new CustomEvent<TSource,TArgs>(s,a)); 

       addHandler(eventHandler); 

       return Disposable.Create(() => removeHandler(eventHandler)); 
      }); 
    } 
} 

A continuación, puede utilizarlo como:

var observable = ObservableEx.FromCustomEvent<BaseSubscription,BaseEvent>(
    h => updateSource.Update += h, 
    h => updateSource.Update -= h 
); 
+0

Esto parece bastante prometedor. Voy a verificar esto. –

1

También se puede simplemente hacer esto de la manera perezosa, si UpdateSource nunca desaparece:

var observable = new Subject<BaseEvent>(); 
updateSource.Update += (o,e) => observable.OnNext(e); 

plan de Jon es probablemente una mejor uno, sin embargo, pero los sujetos pueden ayudarte.

1

Para referencia futura, esto es un ejemplo del uso de la sobrecarga de conversión de FromEvent, utilizando el FileSystemEventHandler como ejemplo:

Dim createWatcher As New FileSystemWatcher With {.Path = "C:\Temp", .EnableRaisingEvents = True} 
    Dim foo = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
     Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)), 
     Sub(ev) AddHandler createWatcher.Created, ev, 
     Sub(ev) RemoveHandler createWatcher.Created, ev) 

    Dim changedEv = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
     Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)), 
     Sub(ev) AddHandler createWatcher.Changed, ev, 
     Sub(ev) RemoveHandler createWatcher.Changed, ev) 

    foo.Subscribe(Sub(e) Console.WriteLine("File {0} created.", e.EventArgs.Name)) 
    changedEv.Subscribe(Sub(e) Console.WriteLine("File {0} changed.", e.EventArgs.Name)) 
0
var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
    // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>) 
    handler => (BaseSubscription sender, BaseEvent e) => handler.Invoke(sender, new UpdateEventArgs(e)), 
    // addHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update += handler, 
    // removeHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update -= handler 
); 

Asegúrese UpdateEventArgs ctor acepta un argumento BaseEvent.

0

Tengo una pesadilla similar porque estoy trabajando con una API de interoperabilidad que usa eventos .net no estándar. Soy un principiante en un montón de cosas, incluyendo genéricos, Funcs, Actions, Observables y Rx, así que creo que mi experiencia sobre cómo entender tales cosas será de algún valor.

Podemos entender su Func(Of EventHandler(Of TEventArgs), TDelegate) conversion entendiendo dónde se usa.

Pero primero debemos entender la firma genérica del método FromEvent.

A continuación se muestra la función de firma del método de extensión FromEvent en VB:

Function FromEvent(Of TDelegate, TEventArgs As EventArgs)(_ 
    conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _ 
    addHandler As Action(Of TDelegate), _ 
    removeHandler As Action(Of TDelegate) _ 
) As IObservable(Of IEvent(Of TEventArgs)) 

Pero yo realmente no uso VB, así que tendrá que recurrir a la C# firma:

IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
    Func<Action<TEventArgs>, TDelegate> conversion, 
    Action<TDelegate> addHandler, 
    Action<TDelegate> removeHandler); 

Analicemos la firma C# línea por línea.

Nota: Habrá ocasiones en que incluiré tipos de datos en expresiones lambda para diferenciar los eventos .net estándar de los eventos no estándar.

Primera parte:

IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(

Esto dice que la función acepta un FromEvent TDelegate y TEventArgs As EventArgs como entrada. Tenga en cuenta que la SALIDA IObservable también tendrá el tipo de TEventArgs Por lo tanto, tiene razón cuando dijo que necesita una clase que envuelva los datos de TDelegate. No sé qué versión estoy usando, pero me permite usar cualquier clase, incluso si no hereda de EventArgs. En caso de que vb no te permita, de todos modos es un trabajo trivial ya que solo está agregando :EventArgs a la clase (Hereda de vb?). Vamos a aplicar esto a su problema:

su C# supuestos:

// notice: not an EventHandler<TEventArgs> public delegate void 
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e); 

// class to wrap the data from the above delegate 
public class UpdateEventArgs:EventArgs {...} 

Aplicando a la primera línea se convierte en:

var updates = FromEvent<UpdateEventHandler, UpdateEventArgs>(

Segunda parte:

A continuación, tenemos tres entradas conversion, addhandler y removehandler:

Func<Action<TEventArgs>, TDelegate> conversion, 
Action<TDelegate> addHandler, 
Action<TDelegate> removeHandler); 

Sabemos que addHandler y removeHandler solo agregan y eliminan al delegado de un evento. Hagamos esos dos primero.

// addHandler (Action<UpdateEventHandler>) 
handler => updateSource.Update += handler, 
// removeHandler (Action<UpdateEventHandler>) 
handler => updateSource.Update -= handler 

Ahora vamos a aplicar nuestro tipo a la parte difícil:

Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion, 

Esta función toma un Action<UpdateEventArgs> como entrada y un delegado UpdateEventHandler es la salida. Vamos a asignar en una variable llamada conversion

Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion 
    = (handlerOfUpdateEventArgs) => 
    { 
     UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) => 
      handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e)); 
     return handler; 
    }; 

Para entender mejor lo que esto, vamos a ver cómo se adjunta un controlador de eventos para un evento de .NET estándar:

someObject.SomeEvent += (object sender,EventArgs args) => { ... }; 

Ahora nos centraremos en su evento estándar .NET no y UpdateEventHandler:

public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e); 
updateSource.Update += (BaseSubscription sender, BaseEvent e) => { ... }; 

Si uno mira hacia atrás en la firma de conversion función, se devuelve un UpdateEventHandler delegado. Esto significa que podemos usar conversion para adjuntarlo al evento Update. Pero antes de que podamos hacer eso, la función conversion necesita un Action<UpdateEventArgs> como entrada antes de que pueda usarse. Vamos a hacer eso ahora:

//EventHandler<EventArgs> similarity. 
Action<UpdateEventArgs> actionUpdateEventArgs = (UpdateEventArgs e) => 
    { 
     //This is were you put your code like in a standard.net event 
     //This is also probably where the Observable.FromEvent() puts 
     //wiring necessary to make it into an IObservable<UpdateEventArgs> 
    }; 

Ahora que tenemos todas las piezas que necesitamos, podemos utilizar conversion similar a un controlador de eventos.

Código
updateSource.Update += conversion(actionUpdateEventArgs); 

dentro actionUpdateEventArgs se llama cada vez que se eleva Update.

Afortunadamente, eso fue suficiente explicación para comprender el parámetro Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion.

Por último, se trata de cómo va a utilizar el método de FromEvent():

Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion 
    = (handlerOfUpdateEventArgs) => 
    { 
     UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) => 
      handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e)); 
     return handler; 
    }; 

var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
    // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>) 
    conversion, 
    // addHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update += handler, 
    // removeHandler (Action<UpdateEventHandler>) 
    handler => updateSource.Update -= handler 
); 

Esto es como lo entendí formar un punto de vista novatos, así que espero que será útil.

Cuestiones relacionadas