書籍転載
文法からはじめる プログラミング言語Microsoft Visual C#入門

C#開発者のための拡張メソッド入門
― 第11章 高度なプログラミング 〜プログラミングの世界を広げる ―

WINGSプロジェクト 高江 賢(監修 山田 祥寛)
2010/11/17

本コーナーは、日経BPソフトプレス発行の書籍『文法からはじめる プログラミング言語Microsoft Visual C#入門』の中から、特にInsider.NET読者に有用だと考えられる章や個所をInsider.NET編集部が選び、同社の許可を得て転載したものです。基本的に元の文章をそのまま転載していますが、レイアウト上の理由などで文章の記述を変更している部分(例:「上の図」など)や、図の位置などを本サイトのデザインに合わせている部分が若干ありますので、ご了承ください。『文法からはじめる プログラミング言語Microsoft Visual C#入門』の詳細は「目次情報ページ」もしくは日経BPソフトプレスのサイトをご覧ください。

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

11.5 拡張メソッド(C#バージョン 3.0以降)

 拡張メソッドとは、既にあるクラスを継承せずにメソッドを追加するものです。派生クラスとして機能を追加するのではなく、あたかも最初からそのメソッドが存在していたかのように、元のクラスのメソッドとして呼び出すことができるのです。

 この機能が強力なのは、ユーザー定義のクラスだけでなく、.NET Frameworkであらかじめ定義されているクラスに対しても機能が追加できるという点です。さらに、継承できないシールクラス(sealedアクセス修飾子が指定されたクラス)にもメソッドを定義できます。ただし、拡張メソッドはクラスの外部から機能を拡張するものなので、元のクラスのprivateメンバにはアクセスできません。

図11-4 拡張メソッド

 拡張メソッドは、独立した静的クラスの静的メソッドとして定義する必要があります。また、追加するメソッドの最初のパラメータには、thisキーワードで拡張するクラスを指定します。

public static 拡張メソッド名( this 拡張すべき型, パラメータリスト )
{
  // 拡張メソッドの定義
}
[構文]拡張メソッドの定義

11.5.1 ユーザー定義のクラスを拡張する

 まず、ユーザー定義のクラスを拡張してみます。なお、この章の以降のサンプルコードでは、System.Core.dllというアセンブリへの参照設定が必要になります(参照設定については11.5.4項で説明します)。それには、ソリューションエクスプローラで[参照設定]をクリックして[プロジェクト]メニューの[参照の追加]をクリックします。図11-5の[参照の追加]ダイアログボックスが表示されるので、一覧から[System.Core]をクリックして[OK]をクリックすると、ソリューションエクスプローラに[System.Core]が追加されます。

図11-5 [参照の追加]ダイアログボックス

 次のサンプルコードでは、TestClassを拡張するために、ExtendTestClassクラスで拡張メソッドcheckJを定義しています。

using System;

// 拡張メソッドを定義したクラス
static class ExtendTestClass
{
  // 拡張メソッド(肥満度判定)
  public static void checkJ(this TestClass t)
  {
    // BMIが25以上を肥満、18未満をやせすぎ、
    // それ以外を標準とする。
    if (25 <= t.BMI())
    {
      Console.WriteLine("肥満です");
    }
    else if (t.BMI() < 18)
    {
      Console.WriteLine("やせすぎです");
    }
    else
    {
      Console.WriteLine("標準です");
    }
  }
}

// 体重、身長を保持してBMI値を出力するクラス
class TestClass
{
  double w;
  double t;

  // 体重、身長を指定して初期化
  public TestClass(double w, double t)
  {
    this.w = w; // キログラム
    this.t = t; // センチメートル
  }

  // BMI値を求める
  public double BMI()
  {
    return this.weight / (this.height * this.height);
  }

  // 体重のプロパティ
  public double weight
  {
    get
    {
      return this.w;
    }
  }

  // 身長のプロパティ
  public double height
  {
    get
    {
      return this.t / 100;
    }
  }
}

class MainClass
{
  public static void Main()
  {
    // 身長と体重を指定(キログラム、センチメートル)
    TestClass a = new TestClass(80, 170);

    // 肥満度の判定(拡張メソッド)
    a.checkJ();  // 出力値:肥満です

    var b = new TestClass(40, 160);
    b.checkJ();  // 出力値:やせすぎです
  }
}
[サンプル]extend1.cs

 TestClassクラスでは、体重と身長を保持するプロパティを定義しています。身長の場合、初期値をセンチメートル単位と見なして、100で割ったメートル単位の値を取得できるようにしています。BMIメソッドは、肥満度を示すBMI値を返します。

 拡張メソッドcheckJでは、BMI値を利用して肥満度の判定を行い、結果を表示します。あたかもTestClassのメソッドであるかのように、a.checkJ()という形で呼び出しています。

11.5.2 .NET Frameworkのクラスを拡張する

 今度は、.NET Frameworkで定義されているSystem.Stringクラスを拡張してみましょう。次のサンプルコードでは、拡張メソッドとして16進数の文字列を数値に変換する処理を定義しています。拡張メソッドのパラメータには、System.Stringのエイリアスであるstringを指定しています。なお、元のクラスに同じ名前のメソッドがあった場合はエラーにはならず、単に拡張メソッドが無視されます。

using System;

public static class StringExtender
{
  // 拡張メソッド(16進文字列を数値型に変換)
  public static int hex2Int(this string s)
  {
    return Convert.ToInt32(s, 16);
  }
}

class Program
{
  static void Main()
  {
    string s = "D3";
    Console.WriteLine(s.hex2Int()); // 出力値:211
  }
}
[サンプル]extend2.cs

11.5.3 拡張メソッドと名前空間

 先ほどのサンプルコードでは名前空間を定義していないため、拡張メソッドも拡張されるクラスも、既定の同一の名前空間にあることになります。

 拡張されるクラスと異なる名前空間で拡張メソッドを定義している場合は、次のようにusingディレクティブで使用したい拡張メソッドが含まれる名前空間の使用を宣言します。拡張メソッドは通常のメソッドのように完全修飾名で指定できないため、どの名前空間の拡張メソッドを使用するかを指定するためには、必ずusingディレクティブが必要です。

 先ほどの「extend2.cs」を書き換えて、拡張メソッドを別の名前空間に定義した場合のサンプルコードを示します。

using System;
using X;

namespace X
{
  public static class StringExtender
  {
    // 拡張メソッド(16進文字列を数値型に変換)
    public static int hex2Int(this string s)
    {
      return Convert.ToInt32(s, 16);
    }
  }
}

class Program
{
  static void Main()
  {
    string s = "D3";
    Console.WriteLine(s.hex2Int()); // 出力値:211
  }
}
[サンプル]extend3.cs

 拡張メソッドは名前空間Xで定義されているため、「using X;」の宣言を削除すると、拡張メソッドが見つからずエラーになります。

 当然ながら、同じ名前空間に同じ名前の拡張メソッドを定義すると、どの拡張メソッドを呼び出すのか特定できないためエラーになります。また、同じ名前の拡張メソッドを含む複数の名前空間の使用を宣言すると、同じ理由によりエラーになります。

11.5.4 参照設定とアセンブリ

 参照設定とは、.NET Frameworkや他のプロジェクトで作成されたクラスライブラリを使う場合に、そのライブラリが含まれるアセンブリを指定するものです。.NET Frameworkの基本的なライブラリについては、特に指定しなくても参照するようになっているため、これまでのサンプルコードでは追加の参照設定は不要でした。また、プロジェクトのテンプレートで「空のプロジェクト」ではなく「コンソールアプリケーション」などを選んだ場合、テンプレートに.NET Frameworkの参照設定の追加が含まれているため、本書のサンプルコードの範囲では参照設定の追加は不要です。「空のプロジェクト」の場合は何も設定されませんので、プログラムに応じて自分で設定する必要があります。

 なお、.NET Frameworkにおけるアセンブリとは、アプリケーションなどを管理する単位となるコンパイル済みのコード群のことです。実行ファイルであるexeファイルやdllファイルと同じ形式ですが、その中にバージョンやセキュリティに関する情報も含まれています。それらの付加情報を利用することで、アセンブリのバージョンの違いによって生じる不具合を回避することができます。

【コラム】System.Linq名前空間

 新規プロジェクトの作成時に各種のテンプレートを使うと、既定でいくつかのusingディレクティブが追加されます。その中の1つであるSystem.Linqは、LINQという機能を使うために必要な名前空間です。System.Linq名前空間にはLINQ特有の機能に加え、コレクションクラス(System.Collections.IEnumerableなど)にクエリ機能を追加するための拡張メソッドの定義が含まれています。それらの拡張メソッドを利用するためには、usingディレクティブでSystem.Linqの使用を宣言する必要があります。

 『文法からはじめる プログラミング言語Microsoft Visual C#入門』の転載は今回のみです。このほかのコンテンツについては、「目次情報ページ」もしくは日経BPソフトプレスのサイトをご覧ください。end of article

インデックス・ページヘ 「文法からはじめる プログラミング言語Microsoft Visual 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 記事ランキング

本日 月間