拡張メソッドを作成するには?[C#/VB].NET TIPS

拡張メソッドを使うと、型定義を直接修正することなく、その型にインスタンスメソッドを追加(したように扱うことが)できる。その作成方法と応用例を見てみよう。

» 2017年11月22日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 とあるクラスに「こんなメソッドがあったらよいのに」というときは、どうしたらよいだろうか? そのクラスを直接改修するという手段の他に、.NET Frameworkでは「拡張メソッド」として実装する方法がある。

POINT 拡張メソッドの作成

拡張メソッドを作成する構文まとめ 拡張メソッドを作成する構文まとめ


 拡張メソッドは、そのクラス自体には全く手を付けることなく、しかし、そのクラスにあたかもインスタンスメソッドが追加されたかのように見せかける。本稿では、拡張メソッドの作り方やその応用を解説する。

 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、拡張メソッドはVisual Studio 2008から利用できるが、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015 以降が必要である。

C#で拡張メソッドを作るには?

 静的クラスに静的メソッドを定義し、その第1引数の前にthisキーワードを付けると、拡張メソッドになる(次のコード)。第1引数の型が、すなわち拡張される対象である(このコードではint型を拡張している)。

using System;
namespace SampleNamespace
{
  public static class SampleExtension
  {
    public static int Add(this int m, int n)
      => m + n;
  }
}

拡張メソッドの例(C#)
このAddメソッドはint型を拡張している(第1引数の「this int」)。

 上のAdd拡張メソッドは次のコードのようにして呼び出す。拡張メソッドの名前空間をusingすると、対象の型(ここではint型)のインスタンスにその拡張メソッドが追加されたかのように使える。

 拡張メソッドの左に書かれているオブジェクト(ここではローカル変数m)が、拡張メソッドの第1引数として渡される。呼び出し時に記述した第1引数(ここでは整数リテラル3)は拡張メソッドの第2引数として渡される。

using System;
using static System.Console;
using SampleNamespace; // 拡張メソッドが定義してある名前空間

class Program
{
  static void Main(string[] args)
  {
    int m = 2;
    int result = m.Add(3);
    WriteLine($"m.Add(3)→{result}");
    // 出力:m.Add(3)→5

#if DEBUG
    ReadKey();
#endif
  }
}

拡張メソッドの使用例(C#)
コンソールアプリの例である。先に定義したAdd拡張メソッドがintオブジェクトに追加されたかのように利用できる。
なお、通常の静的メソッドとして「SampleExtension.Add(m, 3)」のように呼び出すことも可能ではある。

Visual Basicで拡張メソッドを作るには?

 モジュールにメソッドを定義し、それにExtension属性(System.Runtime.CompilerServices名前空間)を付けると、拡張メソッドになる(次のコード)。第1引数の型が、すなわち拡張される対象である(このコードではInteger型を拡張している)。

Imports System.Runtime.CompilerServices

Namespace SampleNamespace
  Module SampleExtension
    <Extension()>
    Public Function Add(m As Integer, n As Integer) As Integer
      Return m + n
    End Function
  End Module
End Namespace

拡張メソッドの例(VB)
このAddメソッドはInteger型を拡張している(第1引数の「As Integer」)。

 上のAdd拡張メソッドは次のコードのようにして呼び出す。拡張メソッドの名前空間をImportsすると、対象の型(ここではInteger型)のインスタンスにその拡張メソッドが追加されたかのように使える。Visual Basicでは、宣言した名前空間にはその先頭に暗黙的にプロジェクト名が付けられる。Importsするときには、プロジェクト名から書き始める必要がある(ここでは「dotNetTips1209VB」がプロジェクト名)。

 拡張メソッドの左に書かれているオブジェクト(ここではローカル変数m)が、拡張メソッドの第1引数として渡される。呼び出し時に記述した第1引数(ここでは整数リテラル3)は拡張メソッドの第2引数として渡される。

Imports System.Console
Imports dotNetTips1209VB.SampleNamespace ' 拡張メソッドが定義してある名前空間

Module Module1
  Sub Main()
    Dim m As Integer = 2
    Dim result As Integer = m.Add(3)
    WriteLine($"m.Add(3)→{result}")
    ' 出力:m.Add(3)→5

#If DEBUG Then
    ReadKey()
#End If
  End Sub
End Module

拡張メソッドの使用例(VB)
コンソールアプリの例である。先に定義したAdd拡張メソッドが、Integerオブジェクトに追加されたかのごとく利用できる。
なお、通常の静的メソッドとして「SampleExtension.Add(m, 3)」のように呼び出すことも可能ではある。

定数リテラルでも使える

 上記の使用例はローカル変数だったが、定数リテラルでも拡張メソッドを使える(次のコード)。

int result = 2.Add(3);
WriteLine($"2.Add(3)→{result}");
// 出力:2.Add(3)→5

Dim result As Integer = 2.Add(3)
WriteLine($"2.Add(3)→{result}")
' 出力:2.Add(3)→5

拡張メソッドを定数リテラルで使う例(上:C#、下:VB)

インタフェースやジェネリックの拡張メソッド

 インタフェースに対しても拡張メソッドは作成できる。また、ジェネリックな拡張メソッドも可能だ(次のコード)。C#/VBのインタフェースは(今のところ)デフォルト実装を持てないのだが、それに近いことが可能なわけである。

// 拡張メソッド
public static bool IsGreaterThan<T>(this IComparable<T> m, T n) where T : struct
  => (0 < m.CompareTo(n));

// 使用例
WriteLine($"{2.IsGreaterThan(3)}"); // 出力:False
WriteLine($"{2.5.IsGreaterThan(2.5)}"); // 出力:False
WriteLine($"{2.7d.IsGreaterThan(2.5d)}"); // 出力:True
WriteLine($"{(new DateTime(2017, 11, 15)).IsGreaterThan(new DateTime(2017, 11, 14))}");
// 出力:True

' 拡張メソッド
<Extension()>
Public Function IsGreaterThan(Of T As Structure)(m As IComparable(Of T), n As T) As Boolean
  Return (0 < m.CompareTo(n))
End Function

' 使用例
WriteLine($"{2.IsGreaterThan(3)}") ' 出力:False
WriteLine($"{2.5.IsGreaterThan(2.5)}") ' 出力:False
WriteLine($"{2.7D.IsGreaterThan(2.5D)}") ' 出力:True
WriteLine($"{(New DateTime(2017, 11, 15)).IsGreaterThan(New DateTime(2017, 11, 14))}")
' 出力:True

ジェネリックなインタフェースに対する拡張メソッドの例(上:C#、下:VB)
IComparable<T>型を拡張している。

メソッドチェーン

 拡張メソッドの使いどころともいえるメリットの1つに、メソッドチェーンがある。

 メソッドチェーンとは、メソッドを鎖のようにどんどん続けていく書き方である。LINQで「sampleData.Where(……省略……).Select(……省略……)」のようにメソッドを繋げて書くやり方だ。

 例えば次のコードのようにAdd拡張メソッドとMultiply拡張メソッドを定義すると、メソッドチェーンできるのである(実際、上に挙げたLINQのWhereメソッドとSelectメソッドは拡張メソッドとして実装されている)。

// 拡張メソッド
public static int Add(this int m, int n)
  => m + n;

public static int Multiply(this int m, int n)
  => m * n;

// 使用例
int result = 2.Add(3).Multiply(4);
WriteLine($"2.Add(3).Multiply(4)→{result}");
// 出力:2.Add(3).Multiply(4)→20

' 拡張メソッド
<Extension()>
Public Function Add(m As Integer, n As Integer) As Integer
  Return m + n
End Function

<Extension()>
Public Function Multiply(m As Integer, n As Integer) As Integer
  Return m * n
End Function

' 使用例
Dim result As Integer = 2.Add(3).Multiply(4)
WriteLine($"2.Add(3).Multiply(4)→{result}")
' 出力:2.Add(3).Multiply(4)→20

拡張メソッドでメソッドチェーンにする例(上:C#、下:VB)

まとめ

 拡張メソッドを作ると、対象の型のコードを修正することなく、その型にインスタンスメソッドを追加できる(ように見える)。拡張メソッドを使うときは、拡張メソッドを定義した名前空間をインポートする。

 なお、拡張メソッドは便利な反面、複数の開発者が勝手に拡張メソッドを追加していくと混乱を招く可能性もある。チーム開発では、拡張メソッドの管理台帳を作ったり、あるいは拡張メソッド専用のプロジェクトを作るなどしてうまく管理しよう。

利用可能バージョン:.NET Framework 3.5以降
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic .NET 処理対象:言語構文
使用ライブラリ:Extension属性(System.Runtime.CompilerServices名前空間)
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0/7.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。