[C#] デリゲートとラムダ式・サンプルコード

デリゲート(delegate)

ラムダ式の前身。

レガシーな記述(とマルチキャスト)

delegate string DelegateName(int arg);

class Concrete
{
  private int number = 0;

  public Concrete(int number)
  {
    this.number = number;
  }
  public string Method(int arg)
  {
    int mult = number * arg;
    Debug.WriteLine(mult);
    return mult.ToString();
  }
}

static void Main(string[] args)
{
  DelegateName d;

  d = new DelegateName(new Concrete(1).Method);
  d += new DelegateName(new Concrete(100).Method);
  d += new Concrete(10000).Method; // new DelegateName(~) は省略可能

  Debug.WriteLine("戻り値 = " + d(123));

  /* 以下のように出力される
   123
   12300
   1230000
   戻り値 = 1230000
   */
}

マルチキャストデリゲート

+= 演算子でデリゲートに複数のメソッドを登録することができる。 登録したメソッドは順番に同期的に実行される。 返り値は最後に追加したデリゲートのものが使われる。

定義済みデリゲート

ジェネリクスによりデリゲートの型を汎用的に利用できるようにした定義済みのデリゲート

class DelegateSubclassesSample
{
  public Action a1; // delegate void Name()
  public Action<int> a2; // delegate void Name(int)
  public Action<float, double> a3; // delegate void Name(float, double)

  public Func<string> f0; // delegate string Name()
  public Func<int, string> f1; // delegate string Name(int)
  public Func<float, double, string> f2;  // delegate string Name(float, double)

  public Predicate<int> p; // delegate void Name(int)

  public Converter<int, long> conv; // delegate long Name(int)

  public Comparison<bool> comp; // delegate int Name(bool, bool)
}

匿名メソッド

※ラムダ式の登場によりこれもレガシーな記述になった。

Action d1 = delegate()
{
  // do something...
};

Action<int> d2 = delegate(int arg)
{
  // do something...
};

Func<uint> d3 = delegate()
{
  // do something & return uint value
  return 0;
};

Func<float, double> d4 = delegate(float arg)
{
  // do something & return double value
  return (double)arg;
};

Action capture = delegate()
{
  d1();
  d2(1);
  d3();
  d4(1.0f);
};
capture();

返り値の型はコンパイラが勝手に推論してくれるので指定しなくても良い

匿名メソッドの省略形

引数がないデリゲートはカッコを省略して記述可能

Action<int, int, int> action = delegate
{
  // do something...
};

デリゲートの非同期呼び出し 1

メインスレッドで終了を待つ場合。

Action action = delegate
{
  Debug.WriteLine("Call Async");
  System.Threading.Thread.Sleep(10000);
};

IAsyncResult asyncResult = action.BeginInvoke(null, null);

System.Threading.Thread.Sleep(1000);
Debug.WriteLine("Main Thread A");
action.EndInvoke(asyncResult);
Debug.WriteLine("Main Thread B");

上のコードは以下のように出力される

"Call Async"
"Main Thread A" // 1秒後くらいに
"Main Thread B" // 10秒後くらいに

BeginInvoke を実行したら EndInvoke を実行すること。

デリゲートの非同期呼び出し 2

終了も非同期的に実行する場合。

Action action = delegate
{
  Debug.WriteLine("Call Async");
};

AsyncCallback callback = new AsyncCallback(delegate(IAsyncResult asyncResult)
  {
    Debug.WriteLine("EndInvoke");
    // IAsyncResult のインスタンスから呼び出し元を取り出し
    // EndInvokeを実行する必要がある。
    ((Action)asyncResult.AsyncState).EndInvoke(asyncResult);
  });

action.BeginInvoke(callback, action); // 非同期コール

"Call Async", "EndInvoke" の順で出力される。

BeginInvoke の最後の引数に渡したオブジェクトは IAsyncResult#AsyncState で取得可能なので、取り出して EndInvoke を実行する。

ラムダ式

delegate をより簡素に記述できる糖衣構文…とは全然違うのだけれど一般的な使用法はそんな感じ。 Expression による構文木がプログラムできるようになるけどここでは説明しない。

Func<string, string> d1;
Func<string, string, string> d2;

d1 = (string arg) => { return string.Format("★{0}", arg); };
Debug.WriteLine(d1("HOGE")); // ★HOGE

// 型はコンパイラが推論するため付けなくても良い。
// その場合は左辺のカッコを外せる
d1 = arg => { return string.Format("★{0}", arg); };
Debug.WriteLine(d1("HOGE")); // ★HOGE

// でも引数が2つ以上になると
// コンパイラが読めなくなるので左辺にカッコをつけなければならない
d2 = (arg1, arg2) => { return string.Format("★{0}{1}", arg1, arg2); };
Debug.WriteLine(d2("foo", "bar")); // ★foobar

// 右辺の波括弧も省略可能
// この場合、return キーワード も省略しなければならない
d1 = arg => string.Format("★{0}", arg);
Debug.WriteLine(d1("HOGE")); // ★HOGE

// 右辺の波括弧は
// 命令が複数になると省略できない
d1 = arg => { arg += arg; return arg; };
Debug.WriteLine(d1("Piyo")); // PiyoPiyo

// 引数がない場合や何も処理しない場合
Action d3 = () => { };
d3();

ラムダ式は値ではなく参照をキャプチャする

int i = 0;
Action action = () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);
action += () => Debug.Write(++i);

action(); // print "123456789"
string str = "Hoge";

Action action = () => str = "Piyo";
action();

Debug.Write(str); // print "Piyo"
// 値をキャプチャしたいときには
// その値用に別に変数を用意してその参照をキャプチャさせてやればいい。
string str = "Hoge";

string capture = str;
Action action = () => Debug.WriteLine(capture);

str = "Piyo";
action(); // print "Hoge"

ラムダ式で Dictionary をソートして取り出す

配列とかコレクションクラスでラムダ式を利用するとスッキリする。

Dictionary<int, string> d = new Dictionary<int, string>();
d[2] = "2, ";
d[0] = "0, ";
d[3] = "3";
d[1] = "1, ";

foreach(KeyValuePair<int, string> pair in d.OrderBy(keyValuePair => keyValuePair.Key))
{
  Debug.Write(pair.Value); // 0, 1, 2, 3
}

Debug.WriteLine("");

foreach (KeyValuePair<int, string> pair in d.OrderByDescending(keyValuePair => keyValuePair.Key))
{
  Debug.Write(pair.Value); // 3, 2, 1, 0,
}

配列の中にある要素が存在するかラムダ式でチェックする

// 配列の中に 0 があるか無いかを調べる
int[] sampleA = { 4, 2, 5, 6, 4, 2, 5, 6};
int[] sampleB = { 0, 1, 2, 3, 4, 5, 6, 7 };

Debug.WriteLine(sampleA.Any(n => n == 0)); // False
Debug.WriteLine(sampleB.Any(n => n == 0)); // True

配列の中身を ConvertAll でいじる

int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Debug.Write(string.Join(", ", Array.ConvertAll(array, i => i.ToString())));

"1, 2, 3, 4, 5, 6, 7, 8, 9" と出力される。

あとで読む

Share
関連記事