Rx - can/should I replace .NET events with Observables?

前端 未结 5 1711
挽巷
挽巷 2020-12-12 18:13

Given the benefits of composable events as offered by the Reactive Extensions (Rx) framework, I\'m wondering whether my classes should stop pushing .NET events, and instead

5条回答
  •  囚心锁ツ
    2020-12-12 18:52

    Ok guys, seeing as how I think it's at least worth a shot to try this, and seeing as how RX's Subject isn't quite what I'm looking for, I've created a new observable that fits my needs:

    • Implements IObservable
    • Implements INotifyPropertyChange to work with WPF/Silverlight binding.
    • Provides easy get/set semantics.

    I call the class Observable.

    Declaration:

    /// 
    /// Represents a value whose changes can be observed.
    /// 
    /// The type of value.
    public class Observable : IObservable, INotifyPropertyChanged
    {
        private T value;
        private readonly List observers = new List(2);
        private PropertyChangedEventHandler propertyChanged;
    
        /// 
        /// Constructs a new observable with a default value.
        /// 
        public Observable()
        {
        }
    
        public Observable(T initalValue)
        {
            this.value = initialValue;
        }
    
        /// 
        /// Gets the underlying value of the observable.
        /// 
        public T Value
        {
            get { return this.value; }
            set
            {
                var valueHasChanged = !EqualityComparer.Default.Equals(this.value, value);
                this.value = value;
    
                // Notify the observers of the value.
                this.observers
                    .Select(o => o.Observer)
                    .Where(o => o != null)
                    .Do(o => o.OnNext(value))
                    .Run();
    
                // For INotifyPropertyChange support, useful in WPF and Silverlight.
                if (valueHasChanged && propertyChanged != null)
                {
                   propertyChanged(this, new PropertyChangedEventArgs("Value"));
                }
            }
        }
    
        /// 
        /// Converts the observable to a string. If the value isn't null, this will return
        /// the value string.
        /// 
        /// The value .ToString'd, or the default string value of the observable class.
        public override string ToString()
        {
            return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
        }
    
        /// 
        /// Implicitly converts an Observable to its underlying value.
        /// 
        /// The observable.
        /// The observable's value.
        public static implicit operator T(Observable input)
        {
            return input.Value;
        }
    
        /// 
        /// Subscribes to changes in the observable.
        /// 
        /// The subscriber.
        /// A disposable object. When disposed, the observer will stop receiving events.
        public IDisposable Subscribe(IObserver observer)
        {
            var disposableObserver = new AnonymousObserver(observer);
            this.observers.Add(disposableObserver);
            return disposableObserver;
        }
    
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add { this.propertyChanged += value; }
            remove { this.propertyChanged -= value; }
        }
    
        class AnonymousObserver : IDisposable
        {
            public IObserver Observer { get; private set; }
    
            public AnonymousObserver(IObserver observer)
            {
                this.Observer = observer;
            }
    
            public void Dispose()
            {
                this.Observer = null;
            }
        }
    }
    

    Usage:

    Consuming is nice and easy. No plumbing!

    public class Foo
    {
        public Foo()
        {
            Progress = new Observable();
        } 
    
        public Observable Progress { get; private set; }
    }
    

    Usage is simple:

    // Getting the value works just like normal, thanks to implicit conversion.
    int someValue = foo.Progress;
    
    // Setting the value is easy, too:
    foo.Progress.Value = 42;
    

    You can databind to it in WPF or Silverlight, just bind to the Value property.

    
    

    Most importantly, you can compose, filter, project, and do all the sexy things RX lets you do with IObservables:

    Filtering events:

    foo.Progress
       .Where(val => val == 100)
       .Subscribe(_ => MyProgressFinishedHandler());
    

    Automatic unsubscribe after N invocations:

    foo.Progress
       .Take(1)
       .Subscribe(_ => OnProgressChangedOnce());
    

    Composing events:

    // Pretend we have an IObservable called IsClosed:
    foo.Progress
       .TakeUntil(IsClosed.Where(v => v == true))
       .Subscribe(_ => ProgressChangedWithWindowOpened());
    

    Nifty stuff!

提交回复
热议问题