-1

In the body of my class, I have this line of code:

private ReactiveCommand<object> _displayCommand = ReactiveCommand.Create();

In the class constructor, I set up a subscription:

_displayCommand.Subscribe(_ =>
{
    MessageBox.Show("Button clicked.");
});

Is it possible to write some sort of extension method to effectively combine these two commands into one, so with a single function call we can call both ReactiveCommand.Create(), and create a subscription using Reactive Extensions (RX)?

This would group all logically related code together, and make the ViewModel much cleaner.

Update

This is what I have so far (thanks to @jt000):

public static ReactiveCommand<object> CreateAndSubscribe(Func<object> fn)
{
    var displayCommand = ReactiveCommand.Create();
    displayCommand.Subscribe<object>(_ => fn.Invoke());
    return displayCommand;
}

private ReactiveCommand<object> _displayCommand = CreateAndSubscribe(() =>
{
    return MessageBox.Show("Hello");
});

public ReactiveCommand<object> DisplayCommand
{
    get { return _displayCommand; }
    protected set { _displayCommand = value; }
}

However, I need to occasionally insert the call .Buffer(TimeSpan.FromSeconds(1). between displayCommand and .Subscribe(fn), and this function is not generic enough to do that. What I really need is some way of passing the entire subscription in to CreateAndSubscribe - perhaps some Func that takes an IObservable?

This means I could use something like the following function call:

private ReactiveCommand<object> _displayCommand = 
    CreateAndSubscribe(o => o.Subscribe(() =>
                              {
                                  return MessageBox.Show("Hello");
                              }));

and if I wanted to insert .Buffer(TimeSpan.FromSeconds(1)):

private ReactiveCommand<object> _displayCommand = 
    CreateAndSubscribe(o => o.Buffer(TimeSpan.FromSeconds(1)).Subscribe(() =>
                              {
                                  return MessageBox.Show("Hello");
                              }));
Contango
  • 76,540
  • 58
  • 260
  • 305
  • 6
    All of these solutions leak the IDisposable returned from Subscribe, which is why this API doesn't exist in the first place – Ana Betts Jan 05 '15 at 00:42
  • @Paul Betts Firstly, fantastic work on ReactiveUI, you are a programming god among us mere mortals :) If you add this as the answer, I'll mark it as the official one. – Contango Jan 06 '15 at 19:11

4 Answers4

4

This API is actually intentionally missing, because there is no elegant way for Create to return both the ReactiveCommand and the IDisposable result from Create. You could do something ugly with out parameters, but it'd end up being pretty cumbersome.

While I definitely also sometimes miss a CreateWithAction or something like it, immediate Subscribes are kind of defeating the composability aspect of RxUI; this API makes it harder to put stuff together, not easier.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • How about updating ReactiveCommand so that it you call IDisposable on it, it calls IDisposable on any subscriptions that it owns? This is elegant, and results in one return value. I might see if I can write this myself and see how it flies. – Contango Jan 09 '15 at 18:43
2

Would this work?

public static ReactiveCommand<object> CreateAndSubscribe(Func<object> fn) 
{
    var displayCommand = ReactiveCommand.Create();
    displayCommand.Subscribe(fn);
    return displayCommand;
}

Usage:

CreateAndSubscribe(_ => MessageBox.Show("hi"));
jt000
  • 3,196
  • 1
  • 18
  • 36
  • Thanks! However, I would like a generic solution, so I need some way of passing the subscription in as a parameter. – Contango Jan 04 '15 at 23:17
  • Thanks. I tried that as well, but I still ran into problems. If I want to insert the call `.Buffer(TimeSpan.FromSeconds(1).` between `displayCommand` and `.Subscribe(fn)`, I can't do it. What I really need is some way of passing the entire subscription in - perhaps some function that takes an IObservable? – Contango Jan 04 '15 at 23:25
1

Yes, but there's not much point because instance members aren't in scope when defining a field. The handlers you pass to Subscribe can't reference any of the view model's fields or call any of its methods. You wouldn't be able to do much more than what you've shown in your example, such as call MessageBox.Show or something like that.

Dave Sexton
  • 2,562
  • 1
  • 17
  • 26
  • From my experiments, I think that that what I need would be in scope. The properties I reference in the RX subscription are effectively lazy properties, so they are initialized on demand. – Contango Jan 04 '15 at 23:31
  • Instance members (fields, properties, methods and events) aren't in scope within the definition of an instance field on the same class. That's just the way it is :) – Dave Sexton Jan 04 '15 at 23:33
0

Put something like this in a static class.

   public static void CreateAndSubscribe(this displayCommand)
    {
        displayCommand = ReactiveCommand.Create();
        displayCommand.Subscribe(_ =>
        {
            MessageBox.Show("Button clicked.");
        });
    }

Access it like any other extension method:

_displayCommand.CreateAndSubscribe();
prospector
  • 3,389
  • 1
  • 23
  • 40
  • Thanks for this answer. However, I would like a generic solution, so I need some way of passing the subscription in as a parameter. I have about 20 of these calls, and it would be nice to have a single generic line which can initialize everything (including set up a subscription). – Contango Jan 04 '15 at 23:21
  • I don't think this would compile (ReativeCommand is a class, not a member of displayCommand). – jt000 Jan 04 '15 at 23:24
  • That still won't work... displayCommand doesn't contain a member "ReactiveCommand" (line 3) – jt000 Jan 04 '15 at 23:29
  • That still won't work :) For 2 reasons: 1) displayCommand would need to be a ref or out since your setting it (which I don't think is possible in an extension method). 2) 99% of the time Create would be setting a null variable, so calling null.CreateAndSubscribe would cause a null reference exception before it would resolve the extension method. I don't think a static extension method would be appropriate here... – jt000 Jan 05 '15 at 14:30