連載:C# 3.0入門

第5回 拡張メソッド

株式会社ピーデー 川俣 晶
2008/08/08
Page1 Page2 Page3

C# 2.0プログラマーの悲劇

 本題に入る前に、よくあるトラブルの事例を見てみよう。C# 3.0を使い始めたC# 2.0プログラマーの話だ。

 C# 2.0プログラマーのA君が、Visual Studio 2005を用いて以下のようなコードを書いたとしよう。整数配列がすべて奇数であるかを確認し、その条件が成立していないときはその旨を出力する内容である。

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] primes = { 2, 3, 5, 7, 11 };

      foreach (int target in primes)
      {
        if (target % 2 == 0)
        {
          Console.WriteLine("偶数を含みます");
          break;
        }
      }
    }
  }
}
リスト1 C# 2.0によるコードその1

 しかし、foreach文でループするのはあまりエレガントではない。そこで、ある条件を満たさない項目が存在することを示すだけなら、条件に当てはまらないものを探せばよいことに気付いた。

 最初に思い付いたのはArray.Findメソッドを使う方法だ。しかし、Array.Findメソッドは項目が見つからないときに、その型の既定の値を返す。つまり、int型なら0を返す。しかしこれでは、0を発見したのか、発見できずに0になったのか分からない。

 結局、A君は値ではなく、インデックスを返すFindIndexメソッドを使ってリスト1を書き直した。

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] primes = { 2, 3, 5, 7, 11 };

      if (Array.FindIndex(primes,
              delegate(int n) { return n % 2 == 0; }) >= 0)
      {
        Console.WriteLine("偶数を含みます");
      }
    }
  }
}
リスト2 C# 2.0によるコードその2

 しかし、このコードにもA君は不満を持っていた。本来「すべての値が奇数である」ことを確認するプログラムだが、実際にソース・コードに書かれているのは、「偶数の値を探す」処理だからだ。

 さて、A君はC# 3.0で配列(=Arrayクラス)に新しいメソッド“All”が追加されたことを知った。Allメソッドとは、「シーケンスのすべての要素が条件を満たしているかどうかを判断」するものであり、まさに「すべての値が奇数である」という意図を示すにふさわしいメソッドに思えた。

 そこで、A君は以下のようなプログラムをVisual Studio 2008で書いて、Allメソッドの挙動を確認した。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] a = { 1, 3 }; // 奇数だけ
      int[] b = { 2, 4 }; // 偶数だけ
      int[] c = { 1, 2 }; // 混在
      Console.WriteLine(a.All(n => n % 2 != 0)); // 出力:True
      Console.WriteLine(b.All(n => n % 2 != 0)); // 出力:False
      Console.WriteLine(c.All(n => n % 2 != 0)); // 出力:False
    }
  }
}
リスト3 C# 3.0でAllメソッドを試す

 これを見て、A君は「まさに求めていたメソッドだ」と確信した。そして、さっそくVisual Studio 2005で開発してきたリスト1のコードをVisual Studio 2008で読み込み、プロジェクトのプロパティで「対象のフレームワーク」を.NET Framework 3.5に変更した。これでAllメソッドが使用できるはずである。

 A君はソース・コードの変更に着手した。しかし、A君はすぐにおかしなことに気付く。配列primesに対してIntelliSense(インテリセンス)を機能させても、Allメソッドがリストされないのだ。A君はおかしいと思いつつ、手動で以下のように書き換えた。ところがコンパイル・エラーとなる。

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] primes = { 2, 3, 5, 7, 11 };

      if (!primes.All(n => n % 2 != 0)) // エラー 1 'System.Array' に 'All' の定義が含まれておらず、型 'System.Array' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。
      {
        Console.WriteLine("偶数を含みます");
      }
    }
  }
}
リスト4 コンパイル不能となったリスト1からの改良コード

 このエラー・メッセージを見て、A君はろうばいした。

「'System.Array' に 'All' の定義が含まれていない」だって? そんなバカな。だって、さっき書いたテスト用のソース(リスト3)はOKだったし、対象となるフレームワークのバージョンも正しく切り替えたはずだ。配列に対するメソッド呼び出しは、System.Arrayクラスのメソッドが使われるはずじゃないのか!?

 これに対する答えは、少々ややこしい。

 まず、A君の以下の認識は正しい。

  • Allメソッドは.NET Framework 3.5で拡張されたメソッドである
  • Allメソッドを使用するには、対象のフレームワークに.NET Framework 3.5を指定しなければならない(.NET Framework 2.0では使えない)
  • Allメソッドは配列(System.Arrayクラス)に対して拡張されたメソッドである

 しかし、以下の認識は正しくない。

  • AllメソッドはSystem.Arrayクラスのメソッドである

 C# 3.0には、既存のクラスを変更せずに、メソッドを追加する機能が付加されている。Allメソッドは、この機能を用いて配列に対して拡張されたメソッドである。.NET Framework 3.5になってSystem.Arrayクラスそのものにメソッドが増えたわけではない。しかし、呼び出し可能なメソッドは増えている。このようなメソッドを「拡張メソッド(Extension methods)」と呼ぶ。

 そして、拡張メソッドを使うためには下準備が必要になる。それを有効にするために、「スイッチを入れる」必要があるのだ。A君のプログラムがコンパイルできなかったのは、「スイッチ」が入っていなかったからである。

 以下の1行をソース・コードの先頭に追加すればそのスイッチがオンになり、Allメソッドは使用できるようになる。

using System.Linq;
Allメソッドを利用するのに必要な記述

 しかし、C# 2.0にどっぷり漬かったプログラマーに対して、これはあまりに難題だ。なぜかといえば、usingキーワードとはC# 1.0から存在する当たり前の機能であり、名前の短縮形を与えるために使われるだけだった。使用できる機能をオン/オフする役割など持っていなかったはずだ。それが「スイッチ」としての機能を持つなど、容易には想像できないだろう。

 しかも、この1行は、Visual Studio 2008で自動的に記述されているため、その重要性に気付きにくい。しかし、これを含まないVisual Studio 2005のソース・コードを持ち込めば、即座に凶悪な牙をむくわけである。

 今回は、この強力だが難解な拡張メソッドを使いこなせるようになろう。分かってしまえば何ら難しいものではない。

 

 INDEX
  C# 3.0入門
  第5回 拡張メソッド
  1.C# 2.0プログラマーの悲劇
    2.拡張メソッドの概要/スイッチなしで機能する例/sealedクラスを拡張する
    3.メソッド呼び出しと型の関係/thisの正体/拡張メソッドを使用すべきとき
 
インデックス・ページヘ  「C# 3.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 記事ランキング

本日 月間