Note: Forums will be making significant UX changes to address key usability improvements surrounding search, discoverability and navigation. To learn more about these changes please visit the announcement which can be found HERE.

已答复 Observable.FromEvent

  • Thursday, December 31, 2009 5:52 PM
     
     
    It's a bit funny to have to specify the event type args and the name of the event in a string. It seems that events have some constraints that date from the Fx 1.0 days that may be relaxable for a much nicer experience. In short, why can't I write

     
    Observable.FromEvent(control.MouseUp)
    
    instead of

    Observable.FromEvent<MouseButtonEventArgs>(control, "MouseUp")
    

All Replies

  • Thursday, December 31, 2009 6:21 PM
     
     
    I found that if you create an observable from an event and get the TEventArgs type parameter wrong, you get a silent failure. For instance, Observable.FromEvent<MouseButtonEventArgs>(control, "MouseMove") simply never produces a notification, though I would have hoped that at the time the reflection is done, I'd get a message something more like "Cannot create Observable<IEvent<MouseButtonEventArgs>> on object "blah"'s event "MouseMove" because that event's handler type IEvent<MouseEventArgs>".
  • Thursday, December 31, 2009 7:55 PM
     
     Answered
    Yes, our three FromEvent operators are a bit cumbersome and it is because of restrictions in the .NET framework that do not make events first-class.  I would absolutely love to write Observable.FromEvent(control.MouseUp) and I would love even more to write just control.MouseUp.  Either one of those requires language changes.

    The unfortunate circumstances do not end at the lack of first-classness of events, the add and remove methods are also not exposed and therefore we cannot write something like:

    Observable.FromEvent(control.MouseUp_add, control.MouseUp_remove)

    So we must write:  Observable.FromEvent(h => control.MouseUp += h, h => control.MouseUp -= h)

    And even if we could then we would still need to specify the event args type because method groups do not have a type and therefore cannot drive type inference.  Perhaps, even more cumbersome is the case when you have an event handler that is not EventHandler<T> but some concrete non-generic type.  We cannot write a generic that embodies this idea, so we are stuck with Observable.FromEvent<MyEventHandlerType, MyEventArgs>(h => h.Invoke, h => E += h, h => E -= h).

    As to the error, it seems that this is a problem only with WPF because the error message you requested is indeed thrown when the arguments do not match, but it seems that this is someone delayed in WPF.  Try it in Windows Forms or a Console Application and you will see the message.

    Hope this helps,
    Wes
    • Marked As Answer by Sebastian Good Thursday, December 31, 2009 9:23 PM
    •  
  • Thursday, December 31, 2009 9:28 PM
     
     
    Many thanks. You're right -- other mismatches of args and events have been noted properly. This particular one just seems to disappear in the background in WPF. Oh well! With the events as observables plus monads-for-the-people goodness you're promulgating, hopefully there will be some momentum in the C# and/or CLR team to make some of this smoother.
  • Thursday, December 31, 2009 10:10 PM
     
     
    I hope so too ;)
  • Friday, January 01, 2010 1:03 PM
     
     
    (..) With the events as observables plus monads-for-the-people goodness you're promulgating, hopefully there will be some momentum in the C# and/or CLR team to make some of this smoother.

    For me IObservable with its thread management and composability options is clearly the better multicast-delegate mechanism. Maybe IObservable will replace legacy .net events some time in the future.
  • Tuesday, January 05, 2010 2:57 PM
     
      Has Code

    Regarding that language change... An overload for FromEvent could take System.Reflection.EventInfo. A compiler feature could provide a very generic code generator that could provide PropertyInfo/EventInfo/etc instances for any type (including those being compiled). This solution would not be specific to RX, and would have many uses elsewhere. The consumer syntax could look like this:

    System.Windows.Forms.Button button;
    var x = Observable.FromEvent(button.Click);
    

    button.Click is a public event, of type EventHandler, defined on the Control class. This is where the compiler feature would come in, "extending" the button class w/a public property named Click of type EventInfo. Visual Studio could filter Intellisense to only show these reflection properties when a method expects PropertyInfo/EventInfo/etc types.

  • Tuesday, January 05, 2010 4:36 PM
     
     
    Hi George,

    But as Wes pointed out, it would be even nicer to write something like:  

    IObservable<IEvent<MouseEventArgs>> clickEvents = button.Click;

    So if C# is going to follow-suit in future versions then they might as well go full out and support an implicit coersion from events to the Observable.FromEvent method.

    That would be some sweet sugar ;)

    - Dave
    http://davesexton.com/blog
  • Tuesday, June 07, 2011 8:01 PM
     
      Has Code

    I tried:

                var o = from mm in Observable.FromEvent(h => frm.MouseMove += h, h => frm.MouseMove -= h)
                             select mm.Location;
    

    and I get the error:

    Error    7    Cannot implicitly convert type 'System.Action' to 'System.Windows.Forms.MouseEventHandler'    ...Program.cs    23    75    RxSample

     


    Kevin Burton
  • Sunday, July 03, 2011 2:49 PM
     
      Has Code

    I also tried :

     

    EventLog myEventLog = new EventLog("SendMailEventLog");
    myEventLog.Source = "SendMailSource";
    var o = Observable.FromEvent<EntryWrittenEventArgs>(x => myEventLog.EntryWritten += x, x => myEventLog.EntryWritten -= x);
    myEventLog.EnableRaisingEvents = true; 
    

     


    and I get the same error:

    Error    1    Cannot implicitly convert type 'System.Action<System.Diagnostics.EntryWrittenEventArgs>' to 'System.Diagnostics.EntryWrittenEventHandler'    C:\C# Projects\SendMailService\SendEmailService\CodeLayout\AuditManager.cs    38    97    SendEmailService

    EDIT: Not the same error though
    • Edited by IsisDen Sunday, July 03, 2011 3:18 PM
    •  
  • Sunday, July 03, 2011 3:06 PM
     
     

    Hi,

    Use FromEventPattern instead.  The old FromEvent method now works on any action, not specifically the event handler "pattern".

    - Dave


    http://davesexton.com/blog
  • Sunday, July 03, 2011 3:18 PM
     
      Has Code

    Reply to myself:

    EventLog myEventLog = new EventLog("Application");
    myEventLog.Source = "SendMailSource";
    
    var o = Observable.FromEventPattern<EntryWrittenEventArgs>
    (myEventLog, "EntryWritten");
    o.Subscribe(evt => { console.WriteLine(evt.EventArgs.Entry.Message); });
    myEventLog.EnableRaisingEvents = true;
    
    It works fine now.

  • Monday, July 04, 2011 8:57 AM
     
      Has Code

    There is another way to avoid magic strings in your code. If events can only appear in the += and -= expressions that's fine, we should only respect that in our API.

    I've created an alternative API to FromEventpattern. Unfortunately it requires few other classes but it's really simple to use.

     

    public partial class MainWindow : Window
    {
     public MainWindow()
     {
      InitializeComponent();
       
      var mouseEnter = new ObservableEventHandler<MouseEventArgs>();
      button.MouseEnter += mouseEnter.Handler;
      mouseEnter.Subscribe((ep) => textBox1.Text = ep.EventArgs.RightButton.ToString());
    
      var click = new ObservableEventHandler<RoutedEventArgs>();
      button.Click += click.Handler;
      click.Subscribe((ep) => textBox1.Text = ep.EventArgs.OriginalSource.ToString());
     }
    
    }
    
    public class ObservableEventHandler<TEventArgs> : IObservable<EventPattern<TEventArgs>> where TEventArgs : EventArgs
    {
     private Action<EventPattern<TEventArgs>> m_Subscriptions;
    
     public void Handler(object sender, TEventArgs e)
     {
      var handler = m_Subscriptions;
      if (handler != null) handler(new EventPattern<TEventArgs>(sender, e));
     }
    
     public IDisposable Subscribe(IObserver<EventPattern<TEventArgs>> observer)
     {
      m_Subscriptions += observer.OnNext;
      return Disposable.Create(() => m_Subscriptions -= observer.OnNext);
     }
    }
    
    

     

    I don't detach from the event but in case of UI I usually can ignore that if the lifetime of my subscription is the same as lifetime of my sources.

    With Handler I can easily attach to many event sources.


    Please let me know what do you think about it.

    Stan


    • Edited by StanislawSwierc Monday, July 04, 2011 3:52 PM I've noticed that I can improve my examples
    •  
  • Monday, July 04, 2011 2:20 PM
     
      Has Code

    Hi Stan,

    It's an interesting idea, although FromEventPattern has another overload that doesn't require the event name to be passed in as a string.  For example:

     

    var click = Observable.FromEventPattern<MouseClickEventHandler, MouseClickEventArgs>(
    	d => d.Invoke, 
    	eh => button.Click += eh, 
    	eh => button.Click -= eh
    );
    

     

    - Dave

     


    http://davesexton.com/blog
  • Monday, July 04, 2011 3:50 PM
     
      Has Code

    Hi Dave,
    You are right, there is a way to achieve type safety at compile time with FromEventPattern but it has one drawback – it’s signature complicated. The first time I saw it in intelisense I didn’t know what to do with it. I had to check some samples in the internet and even now, after some time I use Rx, I check my old code to know exactly how to invoke it. 

    public static IObservable<EventPattern<TEventArgs>> FromEventPattern<TDelegate, TEventArgs>(
     Func<EventHandler<TEventArgs>, TDelegate> conversion, 
     Action<TDelegate> addHandler, 
     Action<TDelegate> removeHandler) 
      where TEventArgs : EventArgs;
    


    That’s why I’ve been looking for an easier API. I’m particularly happy with the Handler method approach because I don’t need to bother about MouseEventHandler and other delegates which are only similar to EventHandler. I don’t need any converters. I can just rely on compiler. Moreover I get contravariance for free.

    // FromEventPatter
    var click = Observable.FromEventPattern<MouseClickEventHandler, MouseClickEventArgs>(
     d => d.Invoke,
     eh => button.Click += eh,
     eh => button.Click -= eh
    );
    
    // ObservableEventHandler
    var click = new ObservableEventHandler<MouseClickEventArgs>();
    button1.MouseEnter += click.Handler;
    

    Thanks

    Stan


  • Monday, July 04, 2011 7:12 PM
     
     

    Hi again,

     

    following is this description in msdn doc, about diff. FromEventHandler method's overloads:

     

     

      Name Description
    Public method Static member FromEventPattern<TEventArgs>(Action<EventHandler<TEventArgs>>, Action<EventHandler<TEventArgs>>) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence with the specified add handler and remove handler.
    Public method Static member FromEventPattern(Action<EventHandler>, Action<EventHandler>) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence with a specified add handler and remove handler.
    Public method Static member FromEventPattern<TDelegate, TEventArgs>(Action<TDelegate>, Action<TDelegate>) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence with the specified add handler and remove handler.
    Public method Static member FromEventPattern(Object, String) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence, using reflection to find an instance event.
    Public method Static member FromEventPattern<TEventArgs>(Object, String) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence, using reflection to find an instance event.
    Public method Static member FromEventPattern(Type, String) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence, using reflection to find a static event.
    Public method Static member FromEventPattern<TEventArgs>(Type, String) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence, using reflection to find a static event.
    Public method Static member FromEventPattern<TDelegate, TEventArgs>(Func<EventHandler<TEventArgs>, TDelegate>, Action<TDelegate>, Action<TDelegate>) Converts a .NET event, conforming to the standard .NET event pattern, to an observable sequence with the specified conversion, add handler and remove handler.

    Now with the question: what is the cost of using reflection, in this case: FromEventPattern<TEventArgs>(Object, String), compared to generic ones?

    Thanks,

    Denis