私があなただったら、Rx が実装されている方法にできる限り近い方法でクラスを実装しようとします。
重要な基本原則の 1 つは、多数の操作を使用して結合される比較的少数の具象クラスを使用することです。したがって、いくつかの基本的なビルディング ブロックを作成し、コンポジションを使用してそれらをすべてまとめる必要があります。
Reflector.NET の下で最初に確認する 2 つのクラスがあります: AnonymousObservable<T>
& AnonymousObserver<T>
。特にAnonymousObservable<T>
、オブザーバブルをインスタンス化するための基礎として Rx 全体で使用されます。実際、そこから派生するオブジェクトを見ると、IObservable<T>
特殊な実装がいくつかありますが、それAnonymousObservable<T>
は汎用目的のみです。
静的メソッドObservable.Create<T>()
は本質的に へのラッパーAnonymousObservable<T>
です。
要件に明らかに適合する他の Rx クラスはBehaviorSubject<T>
. サブジェクトはオブザーバブルとオブザーバーの両方BehaviorSubject
であり、受け取った最後の値を記憶しているため、状況に適合します。
これらの基本的なクラスが与えられれば、特定のオブジェクトを作成するために必要なすべての要素がほぼ揃います。オブジェクトは上記のコードから継承するべきではありませんが、代わりにコンポジションを使用して必要な動作をまとめてください。
ここで、Rx との互換性を高め、より構成可能で堅牢にするために、クラス設計にいくつかの変更を加えることをお勧めします。
Notifier<T>
を使用することを優先して、クラスを削除しますBehaviourSubject<T>
。
Observer<T>
を使用することを優先して、クラスを削除しますAnonymousObserver<T>
。
次に、次ObservableValue<T>
のように変更します。
public class ObservableValue<T> : IObservable<T>, IDisposable
{
public ObservableValue(T initial) { ... }
public T Value { get; set; }
public IDisposable Subscribe(IObserver<T> observer);
public void Dispose();
}
の実装は、メンバーを公開すると&へのアクセスが許可されるため、継承するのではなくObservableValue<T>
ラップします。このクラスは計算ではなく値を表すため、あまり意味がありません。サブスクリプションは、ラップされたを使用してクリーンアップします。BehaviourSubject<T>
IObserver<T>
OnCompleted
OnError
AnonymousObservable<T>
Dispose
BehaviourSubject<T>
次に、次ComputedValue<T>
のように変更します。
public class ComputedValue<T> : IObservable<T>, IDisposable
{
public ComputedValue(IObservable<T> source) { ... }
public T Value { get; }
public IDisposable Subscribe(IObserver<T> observer);
public void Dispose();
}
このComputedValue<T>
クラスはAnonymousObservable<T>
、すべてのサブスクライバーに対してラップsource
し、プロパティの値のローカル コピーを取得するために使用しValue
ます。このメソッドは、オブザーバブルDispose
からサブスクライブを解除するために使用されます。source
これらの最後の 2 つのクラスは、設計に必要と思われる唯一の実際の特定の実装です。これは、Value
プロパティによるものです。
ObservableValues
次に、拡張メソッドの静的クラスが必要です。
public static class ObservableValues
{
public static ObservableValue<T> Create<T>(T initial)
{ ... }
public static ComputedValue<V> Compute<T, U, V>(
this IObservable<T> left,
IObservable<U> right,
Func<T, U, V> computation)
{ ... }
}
Compute
メソッドは、 を使用してAnonymousObservable<V>
計算を実行し、メソッドによって返されるIObservable<V>
のコンストラクターに渡す を生成します。ComputedValue<V>
これらすべてが整ったら、次のコードを記述できます。
var ov1 = ObservableValues.Create(1);
var ov2 = ObservableValues.Create(2);
var ov3 = ObservableValues.Create(3);
var cv1 = ov1.Compute(ov2, (x, y) => x + y);
var cv2 = ov3.Compute(cv1, (x, y) => x * y);
//cv2.Value == 9
ov1.Value = 2;
ov2.Value = 3;
ov3.Value = 4;
//cv2.Value == 20
これが役立つかどうか、および/または私が詳しく説明できることがあればお知らせください。
編集:使い捨ても必要です。
また、特に拡張メソッドでサブスクリプションを管理するために実装する必要がありますAnonymousDisposable
。Reflector.NET を使用して Rx の実装を確認するか、以下の私のバージョンを使用してください。CompositeDisposable
Compute
public sealed class AnonymousDisposable : IDisposable
{
private readonly Action _action;
private int _disposed;
public AnonymousDisposable(Action action)
{
_action = action;
}
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
_action();
}
}
}
public sealed class CompositeDisposable : IEnumerable<IDisposable>, IDisposable
{
private readonly List<IDisposable> _disposables;
private bool _disposed;
public CompositeDisposable()
: this(new IDisposable[] { })
{ }
public CompositeDisposable(IEnumerable<IDisposable> disposables)
{
if (disposables == null) { throw new ArgumentNullException("disposables"); }
this._disposables = new List<IDisposable>(disposables);
}
public CompositeDisposable(params IDisposable[] disposables)
{
if (disposables == null) { throw new ArgumentNullException("disposables"); }
this._disposables = new List<IDisposable>(disposables);
}
public void Add(IDisposable disposable)
{
if (disposable == null) { throw new ArgumentNullException("disposable"); }
lock (_disposables)
{
if (_disposed)
{
disposable.Dispose();
}
else
{
_disposables.Add(disposable);
}
}
}
public IDisposable Add(Action action)
{
if (action == null) { throw new ArgumentNullException("action"); }
var disposable = new AnonymousDisposable(action);
this.Add(disposable);
return disposable;
}
public IDisposable Add<TDelegate>(Action<TDelegate> add, Action<TDelegate> remove, TDelegate handler)
{
if (add == null) { throw new ArgumentNullException("add"); }
if (remove == null) { throw new ArgumentNullException("remove"); }
if (handler == null) { throw new ArgumentNullException("handler"); }
add(handler);
return this.Add(() => remove(handler));
}
public void Clear()
{
lock (_disposables)
{
var disposables = _disposables.ToArray();
_disposables.Clear();
Array.ForEach(disposables, d => d.Dispose());
}
}
public void Dispose()
{
lock (_disposables)
{
if (!_disposed)
{
this.Clear();
}
_disposed = true;
}
}
public IEnumerator<IDisposable> GetEnumerator()
{
lock (_disposables)
{
return _disposables.ToArray().AsEnumerable().GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public bool IsDisposed
{
get
{
return _disposed;
}
}
}