C# のデリゲート

  • デリゲートの機能は、C++の関数ポインタとほぼ同じものです。
public class MySort
{
  public delegate int WhichIsBigger(Object o1, Object o2);//!<デリゲートの宣言

  public void Run(Object[] arrObj, WhichIsBigger delFunc) //!<ユーザの作成したdelFuncを使える
  {
    for (int i = 0; i < arrObj.Length; i++)
    {
      for (int j = i + 1; j < arrObj.Length; j++)
      {
        if (delFunc(arrObj[i], arrObj[j]) > 0)
        {
          Object tmp = arrObj[i]; arrObj[i] = arrObj[j]; arrObj[j] = tmp;
        }
      }
    }
  }
}
  • デリゲートに対して +演算子を使うことで、2つ以上のデリゲートを結合させることができます。これを、マルチキャストデリゲートと言います。
  • あるオブジェクトがイベントの集まりを publish(発行)し、それらのイベントに対して、別のオブジェクトから subscribe(登録)することができます。発行した側のオブジェクトがイベントを発生させると、登録した側の全オブジェクトにそのことが通知されます。
  • キーワード event により、その変数がイベント処理専用であることを、コンパイラに知らせることができます。また、別のクラスからのデリゲートの操作を、+=演算子と-=演算子を用いた追加、削除だけに限定できます。
  • 以下のように、匿名メソッドはデリゲートを経由しなければ呼び出すことができません。イベントに、コードそのものを記述することで、保守性を上げることができます。
public class MyClock
{
  private int m_Second = 0;

  public delegate void SecondChangeHandler(Object clock, int second); //!< デリゲートの宣言
  public event SecondChangeHandler OnSecondChange;

  public void Run()
  {
    While(true)
    {
      if (OnSecondChange != null) OnSecondChange(this, m_Second);
      Thread.Sleep(1000);
      m_Second++;
    }
  }
}

public class MyDisplayClock
{
  public Subscribe(MyClock aClock)
  {
    aClock.OnSecondChange += new Clock.SecondChangeHandler(TimeHasChanged);
  }
  public void TimeHasChanged(Object obj, int second)
  {
    Console.WriteLine("Second = " + second);
  }
}

public class MyAnonymousClock
{
  public Subscribe(MyClock aClock)
  {
    aClock.OnSecondChange += delegate(Object obj, int second) //!<匿名メソッド
    {
      Console.WriteLine("Second = " + second);
    };
  }
}

public class Test
{
  public static void Main()
  {
    MyClock aClock = new MyClock();
    MyDisplayClock dc = new MyDisplayClock();
    dc.Subscribe(aClock); //!<登録
    MyAnonymousClock ac = new MyAnonymousClock();
    ac.Subscribe(ac); //!<登録
    aClock.Run();
  }
}
  • デリゲートのリスト取得するには以下のようにします。
  foreach (SecondChangeHandler handler in OnSecondChange.GetInvocationList())
  {
    //handler(this, secondNow);//!<何か処理を行う
  } 
  • イベントハンドラによっては、イベントの処理に時間がかかり、そのイベントの処理が終了するまで、別のイベントハンドラへの通知が遅れるということが起こってしまいます。そのような問題は、デリゲートを非同期で呼び出すことで回避できます。
public class MyAsynchronousClock
{
  public delegate int SecondChangeHandler();
  public event SecondChangeHandler OnSecondChange;

  public void Run()
  {
    while(true)
    {
      if (OnSecondChange == null) return;

      foreach (SecondChangeHandler del in OnSecondChange.GetInvocationList())
      {
        //!<非同期呼び出し、第1引数は終了時呼び出される関数、第2引数は非同期で呼び出す関数
        del.BeginInvoke(new AsyncCallback(ResultReturned), del);
      }
      Thread.Sleep(1000);
    }
  }

  private void ResultReturned(IAsyncResult iar) //!< 結果を取得するためのコールバックメソッド
  {
    SecondChangeHandler del = (SecondChangeHandler)iar.AsynState; //!< デリゲートを元の型に戻す
    int result = del.EndInvoke(iar); //!< 返り値を取得
    Console.WriteLine("Result = " + result); //!< 出力
  }
}