Unanswered Unsubscribe

  • Wednesday, January 23, 2013 10:15 PM
     
     

    Hello,
    I am looking at the reasoning behind the missing explicit Unsubscribe in the observable definition (http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#Unsubscribing) and I think relying solely on IDisposable is a bit confusing.

    I wish Rx would have an explicit Unsubscribe in addition to the IDisposable similarly to other BCL classes such as SqlConnection. As Jeff Prosise states in Framework Design Guidelines, "implementing close and dispose so that they are semantically equivalent avoid a lot of confusion".

    Thoughts?

    Thanks,
    Pedro

All Replies

  • Thursday, January 24, 2013 8:54 PM
     
      Has Code

    I subscribe (pun intended) to the programming school that worse is better (http://en.wikipedia.org/wiki/Worse_is_better)

    IMHO programing changes so the Framework Design Guidelines which was first published 10 years ago came from the school when everyone knew what Close did but Disposible was unknown.   In order to have an explicit unsubscribe you would have to introduce and entire new class that does nothing different but add an alias name and more code when you wrote a subscription by hand.

    Of course you could add an extension on IDisposible that added unsibscribe, but that adds unsubscribe to every disposible:)

    -jam

            /// <summary>
            /// Unsubscribes from the specified disposable.
            /// </summary>
            /// <param name="disposable">The disposable.</param>
            public static void Unsubscribe(this IDisposable disposable)
            {
                disposable.Dispose();
            }

  • Friday, January 25, 2013 1:10 AM
     
     

    Hi Pedro,

    I haven't read Jeff's Framework Design Guidelines book.  The way that you've quoted Jeff implies that defining two methods for disposal is a common pattern and that Jeff often recommends it.  I doubt that's the case.

    Here's what the Design Guidelines for Developing Class Libraries states verbatim in its Implementing Finalize and Dispose to Clean Up Unmanaged Resources section:

    Customizing a Dispose Method Name

    Occasionally a domain-specific name is more appropriate than Dispose. For example, a file encapsulation might want to use the method name Close. In this case, implement Dispose privately and create a public Close method that calls Dispose. The following code example illustrates this pattern. You can replace Close with a method name appropriate to your domain. This example requires the System namespace.

    So the guideline here is not that you must have two methods to dispose of an object, nor is it even recommended in general.  It only recommends that "occasionally a domain-specific name is more appropriate than Dispose" and if you choose so, then it should have the same exact behavior as the Dispose method.  The latter part is similar to your quote from Jeff Prosise.  In this context, it's clear that the previous implication - the recommendation to often define a domain-specific name along with Dispose - is probably incorrect.  I suspect the point of Jeff's quote in its original context was simply that if you're going to use a domain-specific name, then it should behave exactly like Dispose.

    So the question becomes:

    Is a domain-specific name more appropriate than Dispose for unsubscribing to an observable?

    First, let's consider the domain.  We're talking about an abstract concept that is commonly referred to as a "subscription".  A subscription has two primary functions:

    1. To be referenced.  (E.g., stored in a field for later use.)
    2. To be unsubscribed.

    This could be modeled in basically six ways:

    1. The Action delegate; e.g., Action Subscribe(IObserver<T>)
    2. A custom delegate with exactly the same signature as Action; e.g., public delegate void Unsubscribe();
    3. The IDisposable interface; e.g., IDisposable Subscribe(IObserver<T>)
    4. A custom interface that derives from IDisposable; e.g., public interface IUnsubscribable : IDisposable { void Unsubscribe(); }
    5. A custom interface that doesn't derive from IDisposable; e.g., public interface IUnsubscribable { void Unsubscribe(); }
    6. A custom class that implements IDisposable or IUnsubscribable; e.g., public class Subscription : ?

    The Rx team chose #3.  Here's my guess why:

    • It's more natural (and flexible) for the IObservable<T> interface to use another interface in its definition of its Subscribe method rather than a concrete implementation.  This eliminates #6, and perhaps ##1 and 2.
    • An interface is more flexible than a delegate.  For example, a concrete implementation of an interface can encapsulate internal state and behavior.  This eliminates ##1 and 2.
    • IDisposable is a standard FCL interface that models the exact pattern that is needed, thus it makes the API more flexible; e.g., an instance of a subscription can be assigned to any location that expects an IDisposable.  It makes subscriptions work well with other types of resources from third-party libraries and simplifies the model for general lifetime management of a group of resources; e.g., you could define a field of type ICollection<IDisposable> in the code behind of your Window class and then add subscriptions and other kinds of disposable resources so they'll all get disposed when the Window is closed.  This eliminates #5.
    • It's simpler to implement IDisposable alone than it is to implement both IDisposable and a custom interface defining a single method that is semantically equivalent to Dispose.  The latter may be confusing and deemed unnecessary by implementers.  This eliminates #4.
    • IDisposable is a first-class citizen in Rx.  It's not simply an interface that represents subscriptions.  Any of the types defined in the System.Reactive.Disposables namespace can be used with any implementation of the IDisposable interface.  This also eliminates #4.

    Arguably, the benefit of #4 would be to new Rx developers only.  Perhaps the name Unsubscribe might reduce the learning curve slightly, but only temporarily.  It's in every Rx developers best interest to learn about the types in the System.Reactive.Disposables namespace, so perhaps in the interest of learning about IObservable<T>, it's best to model subscriptions exactly as they are commonly used in real code: as IDisposable.

    So the real question is, does the one and only benefit of #4 outweigh all of its costs?

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Friday, January 25, 2013 1:14 AM Small grammar correction
    •  
  • Friday, January 25, 2013 1:21 PM
     
      Has Code

    @MsdnDev, thanks for reading the book and apologies that it didn't answer all your questions.

    To help me out a little bit, I would like to know why you wish there was an Unsubscribe method?

    In an attempt to help answer here is my experience:

    I noticed that when I first started to work on Rx, many devs including myself were naming the result of a subscribe method inconsistently with what it represented. Most of use were doing something like

    IDisposable productChangeQuery = myProduct.Changed.Subscribe(OnProductChange);

    I found my understanding of Rx was greatly improved when we started renaming these variables to represent what they actually were; the subscription to the query not the query itself.

    IDisposable productChangeSubscription = myProduct.Changed.Subscribe(OnProductChange);

    Now when I dispose of the value it makes a lot more sense, I am disposing of the subscription.

    productChangeSubscription.Dispose();

    I hope this helps.

    Regards

    Lee Campbell


    Lee Campbell http://LeeCampbell.blogspot.com