連載:C# 2.0入門

第5回 匿名メソッドとデリゲート

株式会社ピーデー 川俣 晶
2007/10/02
Page1 Page2 Page3 Page4

引数リストを省略した匿名メソッド

 匿名メソッドを記述する際、引数があっても、その引数を使わない場合は引数リストを省略できる。

using System;

class Program
{
  static void Main(string[] args)
  {
    Action<int> action1
      = delegate(int x) { Console.WriteLine(x); };

    Action<int> action2
      = delegate { Console.WriteLine("Hello World!"); };

    action1(123); // 出力:123
    action2(456); // 出力:Hello World!
  }
}
リスト8 引数リストを省略した匿名メソッド

 Action<int>型はint型の引数を1つ取るデリゲート型である。

 ここで、デリゲートaction2は引数があっても使用しないため、引数リストそのものを書かないという選択が可能である。ただし、outやrefの付いた引数があるなど、引数リストを省略できないケースもある。常に省略できるわけではないので注意しよう。

デリゲートの共変性と反変性

 これは匿名メソッドに限定されない話題だが、関連性があるので取り上げておく。

 以下のソース・コードは、Visual Studio .NET 2003(以下、VS.NET 2003)ではコンパイル・エラーになるが、Visual Studio 2005(以下、VS 2005)ではコンパイルできる。

using System;

abstract class Person
{
  public abstract void SayMyName();
}

class StringNamePerson : Person
{
  string name;

  public override void SayMyName()
  {
    Console.WriteLine("私が{0}です。", name);
  }

  public StringNamePerson(string name)
  {
    this.name = name;
  }
}

delegate Person PersonCreator();

class Program
{
  static StringNamePerson Target()
  {
    return new StringNamePerson("L");
  }

  static void Main(string[] args)
  {
    // オブジェクトを作成する方法を取得する
    // (VS.NET 2003ではエラーになる)
    PersonCreator creator = new PersonCreator(Target);

    // 実際にオブジェクトを作成して使用する
    creator().SayMyName(); // 出力:私がLです。
  }
}
リスト9 デリゲートの共変性の例(VS.NET 2003ではコンパイル・エラーになる)

 ここで、「new PersonCreator(Target);」というデリゲートの作成が問題になる。PersonCreatorデリゲートの戻り値の型はPerson型であるのに対して、メソッドTargetの戻り値の型はStringNamePerson型であるためだ。

 VS.NET 2003は、これを型の不一致と見なして「メソッド 'Program.Target()' はデリゲート型 'Person PersonCreator()' と一致しません。」というエラーとする。しかし、StringNamePersonクラスはPersonクラスを継承しているため、Person型と見なして受け入れてもうまく機能する。VS 2005では、そのようにして受け入れるようになっている。これを「デリゲートの共変性」という。

 ちなみに、

PersonCreator creator = new PersonCreator(Target);

という式は、VS.NET 2003でも構文エラーにしないためにこのように記述しているが、VS 2005では、

PersonCreator creator = Target;

で構わない。

 以上は戻り値の型の場合だが、引数の型は逆により強い型を指定されたメソッドを、より弱い型のデリゲートで受け付ける拡張が行われている。これを「デリゲートの反変性」という。趣旨は同様なので、実例となるサンプル・コードのみ掲載しておく。

using System;

abstract class Person
{
  public abstract void SayMyName();
}

class StringNamePerson : Person
{
  string name;

  public override void SayMyName()
  {
    Console.WriteLine("私が{0}です。", name);
  }

  public StringNamePerson(string name)
  {
    this.name = name;
  }
}

delegate void SayObjectName(StringNamePerson p);

class Program
{
  static void Target(Person p)
  {
    p.SayMyName();
  }

  static void Main(string[] args)
  {
    // VS.NET 2003ではエラーになる
    SayObjectName say = new SayObjectName(Target);

    say(new StringNamePerson("L")); // 出力:私がLです。
  }
}
リスト10 デリゲートの反変性の例(VS.NET 2003ではコンパイル・エラーになる)

 ちなみに、

SayObjectName say = new SayObjectName(Target);

という式は、先ほどと同様に2005では、

SayObjectName say = Target;

で構わない。

デリゲート・インスタンスの等価性

 デリゲートのインスタンスは、それ自体が一種の値なので、==演算子やEqualsメソッドなどで容易に同じであるかを調べられる。その際、匿名メソッドには興味深い特徴があるので解説しておこう。

 まず、別個のコードで生成されたデリゲートは、シグネチャや内容が同じでもイコールとは見なされないことを確認しよう。

using System;

class Program
{
  static void Main(string[] args)
  {
    Action<int> m1 = delegate(int dummy) { };
    Action<int> m2 = delegate(int dummy) { };

    Console.WriteLine(m1 == m2); //出力:False
  }
}
リスト11 別個のコードで生成されたデリゲートの比較

 では、1つのデリゲート生成コードから生成されたデリゲートのインスタンスはイコールと見なされるのだろうか?

using System;

class Program
{
  static void Main(string[] args)
  {
    Action<int>[] methods = new Action<int>[2];

    for (int i = 0; i < 2; i++)
    {
      methods[i] = delegate(int dummy) { };
    }
    Console.WriteLine(methods[0] == methods[1]); // 出力:True
  }
}
リスト12 1つのデリゲート生成コードで2つのデリゲートを作る

 このようにイコールと見なされた。

 だが、ここで安心してはいけない。なぜなら、同じコードから生成されたデリゲートがイコールと見なされないことがあるからだ。

using System;

class Program
{
  static void Main(string[] args)
  {
    Action<int>[] methods = new Action<int>[2];

    for (int i = 0; i < 2; i++)
    {
      int j = i;
      methods[i] = delegate(int dummy) { j++; };
    }
    Console.WriteLine(methods[0] == methods[1]); // 出力:False
  }
}
リスト13 イコールと見なされないデリゲートの例

 ここで追加したのは、変数jである。変数jはループを1回繰り返すごとに新しく誕生する。そして、匿名メソッドは変数jを使用しているので、この変数jはキャプチャされる。実は、キャプチャされている変数が同じでなければ、デリゲートはイコールと見なされないのである。このケースでは、methods[0]がキャプチャしている変数jとmethods[1]がキャプチャしている変数jは別物なので、イコールとは見なされないのである。

 逆に、「j++;」を「i++;」に書き換えると、結果はTrueに変わり、イコールと見なされる。この場合、2つの匿名メソッドがキャプチャしている変数iは同じものであるからだ。

 このような仕様は、イコールの判定が、デリゲートを呼び出して同じ結果を出すか否かの判定に使用できることを意味する。キャプチャした変数が別物であれば、もちろん呼び出して同じ結果になるかどうかは分からない。しかし、同じ変数をキャプチャしていれば、同じ結果になると予測できるわけである。


 INDEX
  C# 2.0入門
  第5回 匿名メソッドとデリゲート
    1.おかずでもデザートでもなくご飯/匿名メソッドとは何か?
    2.上位スコープのアクセス/キャプチャされる変数/キャプチャの本質
  3.引数を省略した匿名メソッド/共変性と反変性/インスタンスの等価性
    4.匿名メソッドで継承を置き換えてみる
 
インデックス・ページヘ  「C# 2.0入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間