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

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

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

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

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

11.5 拡張メソッド(Visual Basic 2008)

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

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

図11-7 拡張メソッド

 拡張メソッドの定義には、System.Runtime.CompilerServices名前空間の拡張属性<Extension()>でマークする必要があります*。この属性の指定によって、Visual Basicのコンパイラは、拡張メソッドであることを判断します。また追加するメソッドの最初のパラメータには、拡張するクラスを指定します。

* 属性は、データ型やメソッド、プロパティなどの要素に注釈を付ける機能です。属性が付いていても、要素自身の機能には何の影響もありません。Visual Basic コンパイラや他のアプリケーションが、属性の情報を参照することで、その要素の使用方法を判断します。ここでの属性も、拡張メソッドであることを、コンパイラに指示するためのもので、それ以上の意味はありません。

<System.Runtime.CompilerServices.Extension()> _
Public Sub 拡張メソッド名(ByVal 引数 As 拡張すべきクラス, パラメータリスト)
   ' 拡張メソッドの定義
End Sub
[構文]拡張メソッドの定義

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

 まず、ユーザー定義のクラスを拡張してみます。次のサンプルコードでは、TestClassを拡張するために、ExtendTestという名前のモジュールで拡張メソッドcheckJを定義しています(モジュールについてはコラム「モジュールとは」を参照してください)。このように、拡張メソッドはモジュールで定義する必要があります。

' 拡張メソッドを定義したModule
Module ExtendTest

  ' 拡張メソッド(肥満度判定)
  <System.Runtime.CompilerServices.Extension()> _
  Public Sub checkJ(ByVal t As TestClass)

    ' BMIが25以上を肥満、18未満をやせすぎ、それ以外を標準とする
    If 25 <= t.BMI() Then
      Console.WriteLine("肥満です")
    ElseIf t.BMI() < 18 Then
      Console.WriteLine("やせすぎです")
    Else
      Console.WriteLine("標準です")
    End If

  End Sub
End Module

' 体重、身長を保持してBMI値を出力するクラス
Class TestClass

  Dim w As Double
  Dim t As Double

  ' 体重、身長を指定して初期化
  Public Sub New(ByVal w As Double, ByVal t As Double)
    Me.w = w    ' キログラム
    Me.t = t    ' センチメートル
  End Sub

  ' BMI値を求める
  Public Function BMI() As Double
    Return Me.weight / (Me.height * Me.height)
  End Function

  ' 体重のプロパティ
  Public ReadOnly Property weight() As Double
    Get
      Return Me.w
    End Get
  End Property

  ' 身長のプロパティ
  Public ReadOnly Property height() As Double
    Get
      Return Me.t / 100
    End Get
  End Property
End Class

Class MainClass
  Public Shared Sub Main()

    ' 身長と体重を指定(キログラム、センチメートル)
    Dim a As New TestClass(80, 170)

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

    Dim b = New TestClass(40, 160)
    b.checkJ()            ' 出力値:やせすぎです

  End Sub
End Class
[サンプル]extend1.vb

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

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

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

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

Module StringExtender

  ' 拡張メソッド(16進文字列を数値型に変換)
  <System.Runtime.CompilerServices.Extension()> _
  Public Function hex2Int(ByVal s As String) As Integer
    Return Convert.ToInt32(s, 16)
  End Function

  ' 拡張メソッド(ToLowerは既に定義されているため無視される)
  <System.Runtime.CompilerServices.Extension()> _
  Public Function ToLower(ByVal s As String) As Integer
    Return Convert.ToInt32(s, 16)
  End Function

End Module

Class MainClass
  Public Shared Sub Main()

    Dim s As String = "D3"

    Console.WriteLine(s.hex2Int())  ' 出力値:211

    ' 16進文字列の変換ではなく、小文字変換になる
    Console.WriteLine(s.ToLower())  ' 出力値:d3

  End Sub
End Class
[サンプル]extend2.vb

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

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

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

 先ほどの「extend2.vb」を書き換えて、拡張メソッドを別の名前空間に定義した場合のサンプルコードを示します。なお、この例ではルート名前空間を「ConsoleApplication」としています。

Imports ConsoleApplication.X

Namespace X
  Public Module StringExtender

    ' 拡張メソッド(16進文字列を数値型に変換)
    <System.Runtime.CompilerServices.Extension()> _
    Public Function hex2Int(ByVal s As String) As Integer
      Return Convert.ToInt32(s, 16)
    End Function

  End Module
End Namespace

Class Program
  Public Shared Sub Main()
    Dim s As String = "D3"
    Console.WriteLine(s.hex2Int())   ' 出力値:211
  End Sub
End Class
[サンプル]extend3.vb

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

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

【コラム】モジュールとは

 一般的な意味でのモジュールとは、ハードウェアやソフトウェアの、ひとまとまりの機能や要素のことを指します。Visual BasicでのModule(モジュール)とは、クラスのように、定義されたコードをまとめたものです。.NETに対応する前のVisual Basicでは、モジュール単位でプログラムを作っていくことが、標準的な作成方法でした。

 しかし、Visual Basic .NET以降では、完全にオブジェクト指向に対応したこともあり、クラスと似て非なるModule(標準モジュール)機能は、(拡張メソッドなど一部の機能を除いて)過去との互換性のためにある機能と言えます。

 標準モジュールがクラスと異なるのは、インスタンス化できないことです。標準モジュールのデータは、1つしか存在しません。標準モジュールで定義された変数やメソッドは、すべて共有(Shared)となり、そのモジュール自身以外のどこにも属さない独立したものになります。つまり、どこでも使えるデータとコードとなるため、オブジェクト指向にはなじまない機能であり、むやみに標準モジュールを使用するのは避けた方がよいでしょう。

 書籍『文法からはじめるプログラミング言語Microsoft Visual Basic入門』の部分転載は今回が最終回です。@ITで公開した部分は「目次情報ページ」の各リンクから参照できます。end of article


インデックス・ページヘ 「文法からはじめるプログラミング言語Microsoft Visual Basic入門」


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

本日 月間