0

I have a third party API that I do not have the source to. I instantiate a callback to an event like this:

using namespace API; // This is where APIClient lives

namespace TestApiClientUI
{
    public partial class Form1 : Form
    {
        APIClient apiClient = new APICLient();
        apiClient.QuoteUpdated += api_ClientUpdated;

        private void api_ClientUpdated(object sender, string s, double b, double a)
        {
        }
    }
}

How do I wrap this into an Rx Observable.FromEvent?

Also, is there a way to do it so the overhead of the wraps copy are as little (zero-copy) as possible?

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
Ivan
  • 7,448
  • 14
  • 69
  • 134

1 Answers1

7

Let's define a struct to store the event args.

struct QuoteUpdate
{
    public string S { get; private set; }
    public double B { get; private set; }
    public double A { get; private set; }
    public QuoteUpdate(string s, double b, double a) : this()
    {
        S = s;
        B = b;
        A = a;
    }
}

Now we can define an IObservable<QuoteUpdate> as follows.

var quoteUpdates = Observable.FromEvent<ApiQuoteHandler, QuoteUpdate>(
    emit => (_, s, b, a) => emit(new QuoteUpdate(s, b, a)),
    handler => apiClient.QuoteUpdated += handler,
    handler => apiClient.QuoteUpdated -= handler);

The first lambda defines a mapping from Action<QuoteUpdate> to ApiQuoteHandler. The action, called emit, is what actually broadcasts a value to the subscribers of the observable we are defining. Think of calling emit(value); as something like foreach (var subscriber in subscribers) { subscriber.OnNext(value); }.

The reason we need this first mapping is because the actual underlying event only knows how to subscribe and unsubscribe ApiQuoteHandler instances. The second and third lambdas are of type Action<ApiQuoteHandler. They are called on subscription and unsubscription, respectively.

When an IObserver<QuoteUpdate> observer subscribes to the IObservable<QuoteUpdate> we have defined, this is what happens:

  1. observer.OnNext (of type Action<QuoteUpdate>) is passed into our first lambda as the variable emit. This creates an ApiQuoteHandler that wraps the observer.
  2. The ApiQuoteHandler is then passed into our second lambda as the variable handler. The handler subscribes to the QuoteUpdated event on your apiClient.
  3. The IDisposable returned from Subscribe method on our IObservable<QuoteUpdate> contains a reference to the ApiQuoteHandler that was created by the first of our three lambdas.
  4. When at a later time this IDisposable is disposed, the third lambda is called with the same ApiQuoteHandler from before, unsubscribing from the underlying event.

Because you need a single IObservable<T> for some T yet you have event args (s, b, a), you must define some struct or class to store the three values. I would not worry about the cost of copying a string reference and two double values.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • I don't understand your example. What is handler? Visual Studio 2012 gives an error on the emit line "Only assignment, call, increment, decrement. await, and new object expressions can be used in a statement" – Ivan Aug 11 '15 at 23:04
  • Visual Studio 2012 gives an error on the emit line "Only assignment, call, increment, decrement. await, and new object expressions can be used in a statement" – Ivan Aug 11 '15 at 23:10
  • On += handler, I get an Error 4 Cannot implicitly convert type 'TestApiClientUI.Form1.QuoteUpdatedDelegate' to 'Api.ApiQuoteHandler' C:\Users\Administrator\Downloads\Api_csharp_example\TestApiClientUI\Form1.cs 99 54 TestApiClientUI – Ivan Aug 11 '15 at 23:23
  • On the struct, the compiler gives Error 1 Backing field for automatically implemented property 'TestApiClientUI.Form1.QuoteUpdate.A' must be fully assigned before control is returned to the caller. Consider calling the default constructor from a constructor initializer. C:\Users\Administrator\Downloads\MtApi_csharp_example\TestApiClientUI\Form1.cs 58 20 TestApiClientUI – Ivan Aug 11 '15 at 23:37
  • I fixed the struct problem but still have the problem on the emit line and the += handler line – Ivan Aug 11 '15 at 23:47
  • @Ivan I apologize for the confusion. The code I wrote was browser code so I hadn't worked out the little problems. Please see my edited answer. All I've done is add the "`: this()`" in the struct and replaced `QuoteUpdatedDelegate` with `ApiQuoteHandler`. Though I didn't say it explicitly, `QuoteUpdatedDelegate` was just meant to be a placeholder for the type of your `ApiClient.QuoteUpdated` event. I had no way of knowing what its type was, so I just made up a name that sounded close enough. – Timothy Shields Aug 12 '15 at 00:08
  • @Ivan I've also added some explanation about what the three lambdas are actually doing. – Timothy Shields Aug 12 '15 at 00:16
  • Try this in visual studio : Place your mouse next to the first handler and double push tab..It should create a "handler" , a method that is ready to receive the quoteUpdated stream. – Fx Mzt Aug 12 '15 at 00:21
  • @FxMzt Yes, but that would just be an event handler. @Ivan is asking about how to wrap his event as an `IObservable`. – Timothy Shields Aug 12 '15 at 00:26
  • I think he didnt "refactor" the handler method ..He just pasted your solution (which looks fine to me). – Fx Mzt Aug 12 '15 at 00:30
  • @FxMzt Yep, that's correct. :) After my edit (now that he revealed the name of his event type) he can just copy paste, but hopefully now that I've added some explanation he can understand why it was actually working code and just needed some modification on his part. – Timothy Shields Aug 12 '15 at 00:31
  • Thanks, that is closer. But there is no such type ApiQuoteHandler ? – Ivan Aug 12 '15 at 00:36
  • @Ivan What is the type of your `QuoteUpdated` event on your `ApiClient`? Use whatever type that is instead of `ApiQuoteHandler`. I can't guess the type because you haven't shown it. – Timothy Shields Aug 12 '15 at 00:37