概要

たとえばGUIアプリケーションで、いろいろなウィンドウが、あるイベントのオブザーバーになっていて、そのイベントが発生したら通知をしてもらいたいとします。こういう場合、たとえば

interface Observer {  void update(); }

ってインタフェースと、イベント発生時にイベントをObserverへ通知するSubjectクラス:

class Subject
{
  private IList<Observer> observers = new List<Observer>();

  public void notifyObservers()
  {
    foreach (var observer in observers)
    {
      observer.update();
    }
  }
  public void addObserver(Observer observer){ observers.Add(observer);}
  public void removeObserver(Observer observer) { observers.Remove(observer); }
}

を作成したりします。んで、それぞれのウィンドウクラスが、このObserverの実装クラスをつくってSubjectに登録、なんてことをやると思います。

あ、C#だったらObserverインタフェースとかじゃなくてdelegateでしょ!ってツッコミはおいておきます。後でやってみます。

まずはベタに基本を作ってみる

さて、各ウィンドウが登録したObserverなインスタンスは、その登録したウィンドウが破棄されたらちゃんとSubjectから破棄する必要があるわけですが、それが自動で出来るように弱参照という機構を使ってみようと思います。

まずは弱参照をつかわないでベタにこんな感じ。フィールドにObserverをもってSubjectに登録するようなウィンドウクラスをつくってます。

abstract class IWindow
{
  public Subject Subject { get; set; }
  public IWindow(Subject subject)
  {
    this.Subject = subject;
  }

  public abstract void init();
}

class MyWindow : IWindow
{
  private Observer observer;

  public MyWindow(Subject subject, String name)
    : base(subject)
  {
    observer = new MyObserver(name);
  }

  public override void init()
  {
    base.Subject.addObserver(observer);
  }
}
class MyObserver : Observer
{
  private String name;
  public MyObserver(String name){this.name = name;}
  public void update()
  {
    Console.WriteLine(name + " にイベントが通知されました");
  }
}

メインクラスはこんな感じ。

class Program
{
  public static void Main(string[] args)
  {
    Subject subject = new Subject();

    // Observerを持ったウィンドウクラスを作成。
    IWindow window1 = new MyWindow(subject, "ウィンドウ1");
    window1.init();

    IWindow window2 = new MyWindow(subject, "ウィンドウ2");
    window2.init();

    // なんかイベントが発生したとかで、subjectがObserverたちに通知。
    subject.notifyObservers();

    window1 = null; //ウィンドウ1への参照がなくなったので、Subjectから削除したいのだけど
    GC.Collect();

    subject.notifyObservers();

  }

}

途中でwindow1をnullにしてウィンドウ1への参照がなくなっても、実行結果は

ウィンドウ1 にイベントが通知されました
ウィンドウ2 にイベントが通知されました
ウィンドウ1 にイベントが通知されました
ウィンドウ2 にイベントが通知されました

ってウィンドウ1のイベントハンドラが呼び出されてしまいます。ココはほんとうはwindow1=nullにしてウィンドウ1がGCされたときに、Subjectからobserverを削除したいわけですね。

で弱参照を使うとどうなるか

さて先のプログラムを弱参照を使って書き替えてみます。弱参照ってのは、WeakReference? というクラスで実装されていて、対象のオブジェクト(ココではobserver)を保持するときにこのクラス経由で保持すると、つまり

WeakReference reference = new WeakReference(observer, false);

こうすると、observerの先のオブジェクトがそのほかのインスタンスからの参照されなくなったとき、そのオブジェクトをGC対象にします。

この弱参照を使って実際に Subject クラスを書き替えてみます。

class Subject
{
  private IList<WeakReference> observers = new List<WeakReference>();

  public void notifyObservers()
  {
    foreach (var observer in observers)
    {
      Observer observerTarget = (Observer)observer.Target;
      if (observerTarget != null)
      {
        observerTarget.update();
      }
      else
      {
        Console.WriteLine("このObserverは削除されたようです");
      }
    }
  }

  public void addObserver(Observer observer) {  observers.Add(new WeakReference(observer, false));}
  //↑参照をWeakReference経由に書き替えました
  public void removeObserver(Observer observer) {observers.Remove(new WeakReference(observer, false)); }
}

これで実行してみます。

ウィンドウ1 にイベントが通知されました
ウィンドウ2 にイベントが通知されました
このObserverは削除されたようです
ウィンドウ2 にイベントが通知されました

となりました。window1をnullにしてGCさせたら、ウィンドウ1のオブジェクトがガベージコレクトされ、それを保持していたWeakReference? のインスタンスから値を

Observer observerTarget = (Observer)observer.Target;

って取りだそうとしたときにnullになりました。これってようするにイベントハンドラを自動でSubjectから登録解除出来たってことですね。。

delegateでもやってみる

さて、Observerインタフェースを用いたWeakReference?の動きは確認できました。通常のインスタンスが誰からも参照されなくなったときWeakReference?で保持しているオブジェクトは破棄されてnullになるわけですね。

JavaならこれでOKです、つぎはC#のdelegateはどうなるかやってみます。ObserverをC#のdelegateで実装します。

コードは下記の通りになりました

public delegate void UpdateHandler();
//interface Observer { void update(); }
//class MyObserver : Observer
class Subject
{
  private IList<WeakReference> observers = new List<WeakReference>();
  public void notifyObservers()
  {
    foreach (var observer in observers)
    {
      // 変数をObserverからdelegateへ変更。
      UpdateHandler observerTarget = (UpdateHandler)observer.Target;
      if (observerTarget != null)
      {
        observerTarget();
      }
      else
      {
        Console.WriteLine("このObserverは削除されたようです");
      }
    }
  }

  //add/removeも型をdelegateへ変更
  public void addObserver(UpdateHandler observer)
  {
    observers.Add(new WeakReference(observer, false));
  }
  public void removeObserver(UpdateHandler observer)
  {
    observers.Remove(new WeakReference(observer, false));
  }
}
class MyWindow : IWindow
{
  public String name { get; set; }
  private UpdateHandler observer;
  public MyWindow(Subject subject, String name)
    : base(subject)
  {
    observer = new UpdateHandler(update);
    this.name = name;
  }

  public override void init()
  {
    base.Subject.addObserver(observer);
  }
  public void update()
  {
    Console.WriteLine(name + " にイベントが通知されました");
  }
}

実行してみると先ほどと同じ結果が得られます。つまりWeakReference?が対象にするのは通常のオブジェクトでもdelegateでも同じってことですね。


この記事は

選択肢 投票
おもしろかった 5  
そうでもない 0  

現在のアクセス:4895


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-03-06 (月) 18:54:57 (897d)