代码之家  ›  专栏  ›  技术社区  ›  Judah Gabriel Himango

RX-我可以/应该用可观测的代替.NET事件吗?

  •  36
  • Judah Gabriel Himango  · 技术社区  · 14 年前

    考虑到 Reactive Extensions (Rx) framework 我想知道我的类是否应该停止推送.NET事件,而是公开Rx观测。

    例如,使用标准.NET事件执行以下类:

    public class Foo
    {
       private int progress;
       public event EventHandler ProgressChanged;
    
       public int Progress
       {
          get { return this.progress; }
          set
          {
             if (this.progress != value)
             {
                this.progress = value;
    
                // Raise the event while checking for no subscribers and preventing unsubscription race condition.
                var progressChanged = this.ProgressChanged;
                if (progressChanged != null)
                {
                    progressChanged(this, new EventArgs());
                }
             }
          }
       }
    }
    

    很多单调的水管。

    此类可以使用某种可观察的替代此功能:

    public class Foo
    {
       public Foo()
       {
           this.Progress = some new observable;
       }
    
       public IObservable<int> Progress { get; private set; }
    }
    

    更少的管道。意图不再被管道细节所掩盖。这似乎是有益的。

    我要问你们的问题是:

    1. 用iobservable<t>值替换标准.NET事件是否好/值得?
    2. 如果我要用一个观察器,我会用什么样的?显然,我需要向它推送值(例如progress.updateValue(…)或其他东西)。
    5 回复  |  直到 14 年前
        1
  •  6
  •   Judah Gabriel Himango    14 年前

    我不建议在有内置主题可以为您管理自己的订户列表。它还消除了携带自己的T的可变副本的需要。

    以下是我的(无评论)版本的解决方案:

    public class Observable<T> : IObservable<T>, INotifyPropertyChanged 
    { 
        private readonly BehaviorSubject<T> values; 
    
        private PropertyChangedEventHandler propertyChanged; 
    
        public Observable() : this(default(T))
        {
        } 
    
        public Observable(T initalValue) 
        { 
            this.values = new BehaviorSubject<T>(initalValue);
    
            values.DistinctUntilChanged().Subscribe(FirePropertyChanged);
        }
    
        public T Value 
        { 
            get { return this.values.First(); } 
            set { values.OnNext(value); } 
        }
    
        private void FirePropertyChanged(T value)
        {
            var handler = this.propertyChanged;
    
            if (handler != null)
                handler(this, new PropertyChangedEventArgs("Value"));
        }
    
        public override string ToString() 
        { 
            return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value."; 
        } 
    
        public static implicit operator T(Observable<T> input) 
        { 
            return input.Value; 
        } 
    
        public IDisposable Subscribe(IObserver<T> observer) 
        { 
            return values.Subscribe(observer);
        } 
    
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 
        { 
            add { this.propertyChanged += value; } 
            remove { this.propertyChanged -= value; } 
        } 
    }
    
        2
  •  19
  •   Ana Betts    14 年前

    对于2,最直接的方法是通过主题:

    Subject<int> _Progress;
    IObservable<int> Progress {
        get { return _Progress; }
    }
    
    private void setProgress(int new_value) {
        _Progress.OnNext(new_value);
    }
    
    private void wereDoneWithWorking() {
        _Progress.OnCompleted();
    }
    
    private void somethingBadHappened(Exception ex) {
        _Progress.OnError(ex);
    }
    

    这样,现在您的“进度”不仅可以在进度发生变化时通知,还可以在操作完成时通知,以及通知是否成功。不过请记住,一旦iobservable通过oncompleted或onerror完成,它就“死了”——您不能再向它发布任何内容。

        3
  •  3
  •   Omer Mor    14 年前

    我会简明扼要地说:

    1. 行为主体

    :)

        4
  •  2
  •   Judah Gabriel Himango    14 年前

    好吧,伙计们,看看我觉得至少值得一试,看看Rx的主题是什么,我不太想看到,我已经创建了一个新的可观察的,适合我的需要:

    • 实现IOBServable<t>
    • 实现InotifyPropertyChange以使用WPF/Silverlight绑定。
    • 提供简单的get/set语义。

    我称之为“可观察类”<t>。

    宣言:

    /// <summary>
    /// Represents a value whose changes can be observed.
    /// </summary>
    /// <typeparam name="T">The type of value.</typeparam>
    public class Observable<T> : IObservable<T>, INotifyPropertyChanged
    {
        private T value;
        private readonly List<AnonymousObserver> observers = new List<AnonymousObserver>(2);
        private PropertyChangedEventHandler propertyChanged;
    
        /// <summary>
        /// Constructs a new observable with a default value.
        /// </summary>
        public Observable()
        {
        }
    
        public Observable(T initalValue)
        {
            this.value = initialValue;
        }
    
        /// <summary>
        /// Gets the underlying value of the observable.
        /// </summary>
        public T Value
        {
            get { return this.value; }
            set
            {
                var valueHasChanged = !EqualityComparer<T>.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"));
                }
            }
        }
    
        /// <summary>
        /// Converts the observable to a string. If the value isn't null, this will return
        /// the value string.
        /// </summary>
        /// <returns>The value .ToString'd, or the default string value of the observable class.</returns>
        public override string ToString()
        {
            return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
        }
    
        /// <summary>
        /// Implicitly converts an Observable to its underlying value.
        /// </summary>
        /// <param name="input">The observable.</param>
        /// <returns>The observable's value.</returns>
        public static implicit operator T(Observable<T> input)
        {
            return input.Value;
        }
    
        /// <summary>
        /// Subscribes to changes in the observable.
        /// </summary>
        /// <param name="observer">The subscriber.</param>
        /// <returns>A disposable object. When disposed, the observer will stop receiving events.</returns>
        public IDisposable Subscribe(IObserver<T> 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<T> Observer { get; private set; }
    
            public AnonymousObserver(IObserver<T> observer)
            {
                this.Observer = observer;
            }
    
            public void Dispose()
            {
                this.Observer = null;
            }
        }
    }
    

    用途:

    消费是美好和容易的。没有水管!

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

    用法很简单:

    // 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;
    

    您可以在WPF或Silverlight中对其进行数据绑定,只需绑定到Value属性。

    <ProgressBar Value={Binding Progress.Value} />
    

    最重要的是,您可以撰写、过滤、项目和执行Rx让您对iobservables所做的所有性感的事情:

    筛选事件:

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

    n次调用后自动取消订阅:

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

    撰写事件:

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

    漂亮的东西!

        5
  •  0
  •   x0n    14 年前

    除了您现有的事件代码可能是terser之外:

        public event EventHandler ProgressChanged = delegate {};
    
        ...
           set {
              ... 
              // no need for null check anymore       
              ProgressChanged(this, new EventArgs());
       }
    

    我想换成 Observable<int> 您只是将复杂性从被调用方转移到调用方。如果我只想读int怎么办?

    -奥辛