鉴于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());
}
}
}
}
}
很多单调的管道。
此类可以使用某种observable来替换此功能:
public class Foo
{
public Foo()
{
this.Progress = some new observable;
}
public IObservable<int> Progress { get; private set; }
}
远不如管道。管道细节不再掩盖意图。这看起来很有用。
我的问题很好StackOverflow人员:
答案 0 :(得分:19)
对于#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完成,它就“死了” - 你不能再向它发布任何内容了。
答案 1 :(得分:6)
如果内置的主题可以为您执行此操作,我建议您不要管理自己的订阅者列表。它还消除了携带自己的可变副本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 :(得分:3)
我会简短地说:
:)
答案 3 :(得分:2)
好的家伙,看到我认为这至少值得尝试一下,看看RX的主题&lt; T&gt;不是我想要的,我创造了一个符合我需求的新观察点:
我将该类称为Observable&lt; T&gt;。
/// <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());
漂亮的东西!
答案 4 :(得分:0)
除了您现有的事件代码可能更简洁:
public event EventHandler ProgressChanged = delegate {};
...
set {
...
// no need for null check anymore
ProgressChanged(this, new EventArgs());
}
我认为通过切换到Observable<int>
,您只是将复杂性从被调用者转移到调用者。如果我只想读取int怎么办?
-Oisin