拡張メソッドを使うと、型定義を直接修正することなく、その型にインスタンスメソッドを追加(したように扱うことが)できる。その作成方法と応用例を見てみよう。
とあるクラスに「こんなメソッドがあったらよいのに」というときは、どうしたらよいだろうか? そのクラスを直接改修するという手段の他に、.NET Frameworkでは「拡張メソッド」として実装する方法がある。
拡張メソッドは、そのクラス自体には全く手を付けることなく、しかし、そのクラスにあたかもインスタンスメソッドが追加されたかのように見せかける。本稿では、拡張メソッドの作り方やその応用を解説する。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
なお、拡張メソッドはVisual Studio 2008から利用できるが、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015 以降が必要である。
静的クラスに静的メソッドを定義し、その第1引数の前にthisキーワードを付けると、拡張メソッドになる(次のコード)。第1引数の型が、すなわち拡張される対象である(このコードではint型を拡張している)。
using System;
namespace SampleNamespace
{
public static class SampleExtension
{
public static int Add(this int m, int n)
=> m + n;
}
}
上の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
}
}
モジュールにメソッドを定義し、それに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
上の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
上記の使用例はローカル変数だったが、定数リテラルでも拡張メソッドを使える(次のコード)。
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のインタフェースは(今のところ)デフォルト実装を持てないのだが、それに近いことが可能なわけである。
// 拡張メソッド
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
拡張メソッドの使いどころともいえるメリットの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
拡張メソッドを作ると、対象の型のコードを修正することなく、その型にインスタンスメソッドを追加できる(ように見える)。拡張メソッドを使うときは、拡張メソッドを定義した名前空間をインポートする。
なお、拡張メソッドは便利な反面、複数の開発者が勝手に拡張メソッドを追加していくと混乱を招く可能性もある。チーム開発では、拡張メソッドの管理台帳を作ったり、あるいは拡張メソッド専用のプロジェクトを作るなどしてうまく管理しよう。
利用可能バージョン:.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]
Copyright© Digital Advantage Corp. All Rights Reserved.