オブザーバーを実装する方法

オブザーバーの設計パターンでは、通知を登録するオブザーバーと、データを監視して 1 つ以上のオブザーバーに通知を送信するプロバイダーとの間の分割が必要です。 この記事では、オブザーバーを作成する方法について説明します。 関連記事「 プロバイダーを実装する方法」では、プロバイダーを作成する方法について説明します。

オブザーバーを作成する

オブザーバーを作成するには、 System.IObserver<T> インターフェイスを実装します。 次の手順では、定義する必要がある各メンバーについて説明します。

  1. System.IObserver<T> インターフェイスを実装するオブザーバー型を定義します。

    次のコードでは、TemperatureReporterのジェネリック型引数を使用して構築されたSystem.IObserver<T>実装であるTemperatureという名前の型を定義します。

    namespace TemperatureSample;
    
    public sealed class TemperatureReporter : IObserver<Temperature>
    
    Namespace Global.TemperatureSample
    
        Public NotInheritable Class TemperatureReporter
            Implements IObserver(Of Temperature)
    
  2. プロバイダーがIObserver<T>.OnCompletedを呼び出す前にオブザーバーの登録を解除する必要がある場合は、IDisposableによって返されるIObservable<T>.Subscribeを保持するプライベート変数を定義し、サブスクリプション メソッドを定義します。

    プライベート変数unsubscriberIDisposable オブジェクトを格納します。 Subscribe メソッドは、プロバイダーのSubscribe メソッドを呼び出し、返されたオブジェクトをunsubscriberに割り当てます。

    namespace TemperatureSample;
    
    public sealed class TemperatureReporter : IObserver<Temperature>
    {
        private IDisposable? _unsubscriber;
        private Temperature? _last;
    
        public void Subscribe(IObservable<Temperature> provider)
        {
            ArgumentNullException.ThrowIfNull(provider);
            _unsubscriber = provider.Subscribe(this);
        }
    
    Namespace Global.TemperatureSample
    
        Public NotInheritable Class TemperatureReporter
            Implements IObserver(Of Temperature)
    
            Private _unsubscriber As IDisposable
            Private _last As Temperature?
    
            Public Sub Subscribe(provider As IObservable(Of Temperature))
                ArgumentNullException.ThrowIfNull(provider)
                _unsubscriber = provider.Subscribe(Me)
            End Sub
    
  3. プロバイダーがUnsubscribeを呼び出す前に、オブザーバーが通知の受信を停止できるようにするIObserver<T>.OnCompleted メソッドを定義します。

    public void Unsubscribe() => _unsubscriber?.Dispose();
    
    Public Sub Unsubscribe()
        _unsubscriber?.Dispose()
    End Sub
    
  4. IObserver<T>で定義されている 3 つのメソッド (IObserver<T>.OnNextIObserver<T>.OnErrorIObserver<T>.OnCompleted) を実装します。

    OnErrorメソッドと OnCompleted メソッドは、スタブ実装にすることができます。 OnError メソッドは、渡されたExceptionを例外として処理しないでください。また、OnCompletedはプロバイダーのIDisposable.Dispose実装を呼び出すことができます。

    public void OnCompleted() =>
        Console.WriteLine("Additional temperature data won't be transmitted.");
    
    // OnError is informational; observers shouldn't treat it as an exception to handle.
    public void OnError(Exception error) { }
    
    public void OnNext(Temperature value)
    {
        Console.WriteLine($"The temperature is {value.Degrees}°C at {value.Date:g}");
    
        if (_last is Temperature previous)
        {
            TimeSpan elapsed = value.Date.ToUniversalTime() - previous.Date.ToUniversalTime();
            Console.WriteLine($"   Change: {value.Degrees - previous.Degrees}° in {elapsed:g}");
        }
    
        _last = value;
    }
    
    Public Sub OnCompleted() Implements IObserver(Of Temperature).OnCompleted
        Console.WriteLine("Additional temperature data won't be transmitted.")
    End Sub
    
    ' OnError is informational; observers shouldn't treat it as an exception to handle.
    Public Sub OnError(error_ As Exception) Implements IObserver(Of Temperature).OnError
    End Sub
    
    Public Sub OnNext(value As Temperature) Implements IObserver(Of Temperature).OnNext
        Console.WriteLine($"The temperature is {value.Degrees}°C at {value.Date:g}")
    
        If _last.HasValue Then
            Dim previous = _last.Value
            Dim elapsed = value.Date.ToUniversalTime() - previous.Date.ToUniversalTime()
            Console.WriteLine($"   Change: {value.Degrees - previous.Degrees}° in {elapsed:g}")
        End If
    
        _last = value
    End Sub
    

完全なコード例

次の例は、温度監視アプリケーションのTemperatureReporter実装を提供する、IObserver<T> クラスの完全なソース コードを示しています。

namespace TemperatureSample;

public sealed class TemperatureReporter : IObserver<Temperature>
{
    private IDisposable? _unsubscriber;
    private Temperature? _last;

    public void Subscribe(IObservable<Temperature> provider)
    {
        ArgumentNullException.ThrowIfNull(provider);
        _unsubscriber = provider.Subscribe(this);
    }

    public void Unsubscribe() => _unsubscriber?.Dispose();

    public void OnCompleted() =>
        Console.WriteLine("Additional temperature data won't be transmitted.");

    // OnError is informational; observers shouldn't treat it as an exception to handle.
    public void OnError(Exception error) { }

    public void OnNext(Temperature value)
    {
        Console.WriteLine($"The temperature is {value.Degrees}°C at {value.Date:g}");

        if (_last is Temperature previous)
        {
            TimeSpan elapsed = value.Date.ToUniversalTime() - previous.Date.ToUniversalTime();
            Console.WriteLine($"   Change: {value.Degrees - previous.Degrees}° in {elapsed:g}");
        }

        _last = value;
    }
}
Namespace Global.TemperatureSample

    Public NotInheritable Class TemperatureReporter
        Implements IObserver(Of Temperature)

        Private _unsubscriber As IDisposable
        Private _last As Temperature?

        Public Sub Subscribe(provider As IObservable(Of Temperature))
            ArgumentNullException.ThrowIfNull(provider)
            _unsubscriber = provider.Subscribe(Me)
        End Sub

        Public Sub Unsubscribe()
            _unsubscriber?.Dispose()
        End Sub

        Public Sub OnCompleted() Implements IObserver(Of Temperature).OnCompleted
            Console.WriteLine("Additional temperature data won't be transmitted.")
        End Sub

        ' OnError is informational; observers shouldn't treat it as an exception to handle.
        Public Sub OnError(error_ As Exception) Implements IObserver(Of Temperature).OnError
        End Sub

        Public Sub OnNext(value As Temperature) Implements IObserver(Of Temperature).OnNext
            Console.WriteLine($"The temperature is {value.Degrees}°C at {value.Date:g}")

            If _last.HasValue Then
                Dim previous = _last.Value
                Dim elapsed = value.Date.ToUniversalTime() - previous.Date.ToUniversalTime()
                Console.WriteLine($"   Change: {value.Degrees - previous.Degrees}° in {elapsed:g}")
            End If

            _last = value
        End Sub
    End Class

End Namespace