この記事では、.NET C# のオブザーバー デザイン パターンといくつかの機能強化について説明します。 の定義 オブザーバーの設計パターン 最も重要で一般的に使用されるデザイン パターンの 1 つです。 Observer デザイン パターンは、 まず、 の正式な定義を確認しましょう。 Observer Design Pattern 通り : マイクロソフトのドキュメント オブザーバー デザイン パターンを使用すると、サブスクライバーはプロバイダーに登録して、プロバイダーからの通知を受け取ることができます。プッシュベースの通知を必要とするあらゆるシナリオに適しています。このパターンは、プロバイダー (サブジェクトまたはオブザーバブルとも呼ばれます) と、ゼロ、1 つ、または複数のオブザーバーを定義します。オブザーバーはプロバイダーに登録され、定義済みの条件、イベント、または状態の変化が発生するたびに、プロバイダーはメソッドの 1 つを呼び出して、すべてのオブザーバーに自動的に通知します。このメソッド呼び出しでは、プロバイダーはオブザーバーに現在の状態情報を提供することもできます。 .NET では、オブザーバー デザイン パターンは、汎用の および インターフェイスを実装することによって適用されます。ジェネリック型パラメーターは、通知情報を提供する型を表します。 System.IObservable<T> System.IObserver<T> したがって、上記の定義から、次のことが理解できます。 2 つのパーティまたはモジュールがあります。 提供する情報のストリームを持つモジュール。このモジュールは、 (情報を提供するため)、 (情報を外部に公開するため)、または (外部から監視できるため) と呼ばれます。 Provider Subject Observable 他の場所からの情報の流れに関心を持つモジュール。このモジュールは と呼ばれます (情報を監視するため)。 Observer オブザーバー デザイン パターンの利点 現在わかっているように、 、 モジュールと モジュールの間の関係を定式化します。 のユニークな点は、それを使用すると、密結合関係を持たなくてもこれを実現できることです。 Observer Design Pattern は Observable Observer オブザーバー デザイン パターン パターンの仕組みを分析すると、次のことがわかります。 について必要な最小限の情報を知っています。 Observable は、 Observer について必要な最小限の情報を知っています。 Observer は、 Observable 相互認識でさえ、具体的な実装ではなく、抽象化によって達成されます。 最後に、両方のモジュールがそれぞれの仕事を行うことができ、それぞれの仕事だけを行うことができます。 使用される抽象化 これらは、 で を実装するために使用される です。 .NET C# Observer デザイン パターン 抽象化 IObservable<out T> これは、任意の を表す インターフェースです。 .NET の Variance について詳しく知りたい場合は、こちらの記事をご覧ください。 . Observable Covariant .NET C# の共分散と反分散 このインターフェイスで定義されているメンバーは次のとおりです。 public IDisposable Subscribe (IObserver<out T> observer); メソッドを呼び出して、 その情報ストリームに関心を持っていることを に通知する必要があります。 Subscribe Observer が Observable メソッドは、 インターフェイスを実装するオブジェクトを返します。その後、このオブジェクトを が使用して、 によって提供される情報のストリームからサブスクライブを解除できます。これが完了すると、 情報のストリームに対する更新について通知されなくなります。 Subscribe IDisposable Observer Observable オブザーバーは IObserver<in T> これは、任意の を表す インターフェースです。 .NET の Variance について詳しく知りたい場合は、こちらの記事をご覧ください。 . Observer Contravariant .NET C# の共分散と反分散 このインターフェイスで定義されているメンバーは次のとおりです。 public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value); メソッドは、 によって呼び出され、情報のストリームが完了し、 それ以上の情報を期待しないことを に通知する必要があります。 OnCompleted Observable Observer が Observer メソッドは、 によって呼び出され、 にエラーが発生したことを通知する必要があります。 OnError Observable Observer メソッドは、 によって呼び出され、新しい情報の準備ができてストリームに追加されていることを に通知する必要があります。 OnNext Observable Observer マイクロソフトの実装 それでは、 C# で実装することをどのように推奨しているかを見てみましょう。後で、私が自分で実装したマイナーな拡張機能をいくつか紹介します。 Microsoft が Observer デザイン パターンを 簡単な を作成します。このアプリケーションには、 モジュール (Observable、Provider、Subject) と モジュール (Observer) があります。 天気予報コンソール アプリケーション WeatherForecast WeatherForecastObserver それでは、実装の検討を始めましょう。 ウェザーインフォ namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } } これは、情報ストリームに流れる情報を表すエンティティです。 天気予報 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } ここで気付くこと: クラスは を実装しています。 WeatherForecast IObservable<WeatherInfo> メソッドの実装では、渡された Observer が以前に登録されていたかどうかを確認します。そうでない場合は、ローカルの オブザーバー リストに追加します。次に、ローカルの リストにあるすべての エントリを 1 つずつループし、Observer の メソッドを呼び出して Observer に通知します。 Subscribe m_Observers m_WeatherInfoList WeatherInfo OnNext 最後に、情報ストリームからの登録解除のために Observer が使用する クラスの新しいインスタンスを返します。 WeatherForecastUnsubscriber メソッドは、メイン モジュールが新しい を登録できるように定義されています。現実の世界では、これは内部のスケジュールされた 、または へのリスナー、または情報源として機能するその他のものに置き換えることができます。 RegisterWeatherInfo WeatherInfo API 呼び出し SignalR Hub Unsubscriber<T> using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } ここで気付くこと: これは、Un-subscriber の基本クラスです。 を適用して を実装します。 Disposable Design Pattern IDisposable コンストラクターを介して、オブザーバーの完全なリストと、それが作成されたオブザーバーを受け取ります。 廃棄中に、オブザーバーがオブザーバーの完全なリストに既に存在するかどうかを確認します。はいの場合、リストから削除します。 天気予報購読解除 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } } ここで気付くこと: これは クラスから継承しています。 Unsubscriber<T> 特別な取り扱いはありません。 WeatherForecastObserver using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } ここで気付くこと: クラスは を実装しています。 WeatherForecastObserver IObserver<WeatherInfo> メソッドでは、温度をコンソールに書き込みます。 OnNext メソッドでは、「完了」をコンソールに書き込みます。 OnCompleted メソッドでは、「エラー」をコンソールに書き込みます。 OnError メインモジュールが登録プロセスをトリガーできるように、 メソッドを定義しました。返されたサブスクライバー解除オブジェクトは、サブスクライブ解除の場合に使用するために内部的に保存されます。 void Subscribe(WeatherForecast provider) 同じ概念を使用して、 メソッドが定義され、内部的に保存されたサブスクライバー解除オブジェクトを利用します。 void Unsubscribe() プログラム using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } ここで気付くこと: プロバイダーのインスタンスを作成しました。 次に、3つの情報を登録しました。 この時点まで、オブザーバーが定義されていないため、コンソールには何も記録されません。 次に、オブザーバーのインスタンスを作成しました。 次に、オブザーバーをストリームにサブスクライブしました。 この時点で、コンソールに記録された 3 つの温度が表示されます。これは、オブザーバーがサブスクライブすると、既存の情報について通知されるためです。私たちの場合、それらは 3 つの情報です。 次に、2 つの情報を登録します。 そのため、さらに 2 つのメッセージがコンソールに記録されます。 次に、購読を解除します。 次に、1 つの情報を登録します。 ただし、オブザーバーが既にサブスクライブを解除しているため、この情報はコンソールに記録されません。 その後、オブザーバーは再びサブスクライブします。 次に、1 つの情報を登録します。 したがって、この情報はコンソールに記録されます。 最後に、これを実行すると、次の結果になります。 私の拡張実装 Microsoft の実装を確認したところ、いくつかの懸念事項が見つかりました。そのため、いくつかの小さな変更を行うことにしました。 IExtendedObservable<out T> using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } } ここで気付くこと: インターフェイスは、 インターフェイスを拡張します。 IExtendedObservable<out T> IObservable<T> です。詳しく知りたい方はこちらの記事もチェック . 共変 .NET C# の共分散と反分散 プロパティを定義して、他のモジュールがサブスクライブしなくても既存の情報エントリのインスタント リストを取得できるようにしました。 IReadOnlyCollection<T> Snapshot また、追加の パラメーターを使用して メソッドを定義し、オブザーバーがサブスクライブ時に既存の情報エントリについて通知を受けるかどうかを決定できるようにしました。 bool withHistory IDisposable Subscribe(IObserver<T> observer, bool withHistory) 退会者 using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } ここで気付くこと: 現在、 クラスはジェネリックではありません。 Unsubscriber これは、情報エンティティの型を知る必要がなくなったためです。 オブザーバーの完全なリストとそれが作成されたオブザーバーにアクセスする代わりに、オブザーバブルが破棄されたときにオブザーバブルに通知し、オブザーバブルが登録解除プロセスを単独で処理します。 このように、それは以前よりも少なくなり、その仕事をしているだけです. 天気予報購読解除 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } } ここで気付くこと: から 部分を削除しました。 Unsubscriber<T> <T> そして今、コンストラクターは、破棄の場合に呼び出される を受け取ります。 Action 天気予報 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } ここで気付くこと: 内部の リストを返す プロパティ以外はほとんど同じですが、 と同じです。 m_WeatherInfoList IReadOnlyCollection<WeatherInfo> Snapshot IReadOnlyCollection そして パラメータを利用する メソッド。 withHistory IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) WeatherForecastObserver using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } ここで気付くのは、 を除いてほぼ同じであり、履歴を使用して するかどうかを決定することです。 Subscribe(WeatherForecast provider) Subscribe プログラム using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } 以前と同じです。 最後に、これを実行すると、以前と同じ結果になるはずです。 次は何ですか これで、.NET C# の の基本を理解できました。しかし、これで話は終わりではありません。 オブザーバー デザイン パターン および インターフェイスの上に構築されたライブラリがあり、便利な機能を提供します。 IObservable<T> IObserver<T> これらのライブラリの 1 つは、 図書館。これは、非同期プログラミングをサポートする一連の拡張メソッドと LINQ 標準シーケンス演算子で構成されています。 .NET (Rx) のリアクティブ拡張機能 したがって、これらのライブラリを調べて試してみることをお勧めします。私はあなたがそれらのいくつかを好きになると確信しています. こちらにも掲載。