連載:[完全版]究極のC#プログラミング

Chapter14 拡張メソッド

川俣 晶
2010/03/01

 本記事は、(株)技術評論社が発行する書籍『[完全版]究極のC#プログラミング ― 新スタイルによる実践的コーディング』から、許可を得て転載しています。
 同書籍は、もともと本フォーラムにて連載していた『C# 2.0入門』、『C# 3.0入門』の記事を整理統合し、加筆、修正されたものです。

手元でまとめて読みたい方は、ぜひ書店などにてお買い求めください。

 【注意】本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

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

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

 C# 2.0プログラマーのA君が、Visual Studio 2005を用いて次のリスト14.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 };

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

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

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

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

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)
              // ラムダ式なら「(n)=>n % 2 == 0) >= 0)」に相当
      {
        Console.WriteLine("偶数を含みます");
      }
    }
  }
}
リスト14.2 C# 2.0によるコード−その2

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

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

 そこで、A君は、次のリスト14.3のプログラムを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
    }
  }
}
リスト14.3 C# 3.0でAllメソッドを試す

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

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

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("偶数を含みます");
      }
    }
  }
}
リスト14.4 コンパイル不能となったリスト14.1からの改良コード

 このエラーメッセージを見て、A君は狼狽した。

「『'System.Array' に 'All' の定義が含まれていない』だって? そんなバカな。だって、さっき書いたテスト用のソース(リスト14.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)と呼ぶ。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter14 拡張メソッド
  1.14.1 C# 2.0プログラマーの悲劇
    2.14.2 Allメソッドを利用するのに必要な記述
    3.14.3 拡張メソッドの概要
    4.14.4 スイッチなしで機能する例
    5.14.5 sealedクラスを拡張する
    6.14.6 拡張メソッドはオブジェクト内部に手出しできない
    7.14.7 拡張メソッドはオブジェクトの振る舞いを変更できない
    8.14.8 拡張メソッドが安全である理由
    9.14.9 メソッド呼び出しと型の関係
    10.14.10 thisの正体
    11.14.11 拡張メソッドを使用すべきとき
    12.14.12 コレクションに拡張されるメソッド
    13.14.13 なぜ「using System.Linq;」なのか?/練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


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 記事ランキング

本日 月間