拡張メソッド

既存のクラスの機能拡張

C#の組み込みのデータ型や標準ライブラリ、他人が作成したライブラリなどは使用する側が内部のコードをいじくることはできません。
それらを利用した処理でよく使用するものは、メソッドにして自作のクラスなどにまとめておくのが普通の方法です。

例えば小数型データを少し使いやすくするためのメソッド群をまとめたクラスを作ってみます。


public static class MathUtil
{
    static readonly double tolerance = 0.0001;

    //許容誤差を使用した小数型の比較
    //誤差がtolerance未満なら等しいとみなす
    public static bool Approximately(double val1, double val2)
    {
        return Math.Abs(val1- val2) < tolerance;
    }

    //一般的な四捨五入を行う
    public static double Round(double val, int digits = 0)
    {
        return Math.Round(val, digits, MidpointRounding.AwayFromZero);
    }
}

static void Main(string[] args)
{
    Console.WriteLine
        (MathUtil.Approximately(0.0001, 0.00001));

    Console.WriteLine(MathUtil.Round(1234.5));

    Console.ReadLine();
}
True
1235

これだけでも何ら問題はありませんが、拡張メソッドを使用するとさらに便利にこれらのメソッドを呼び出すことができます。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public static class MathExtension
    {
        static readonly double tolerance = 0.0001;
    
        //許容誤差を使用した小数型の比較
        //誤差がtolerance未満なら等しいとみなす
        public static bool Approximately(this double val1, double val2)
        {
            return Math.Abs(val1 - val2) < tolerance;
        }
    
        //一般的な四捨五入を行う
        public static double Round(this double val, int digits = 0)
        {
            return Math.Round(val, digits, MidpointRounding.AwayFromZero);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            //まるでインスタンスメソッドのように
            //呼び出せる
            Console.WriteLine
                ((0.0001).Approximately(0.00001));
    
            Console.WriteLine((1234.5).Round());
    
            Console.ReadLine();
        }       
    }
}
True
1235

上記の拡張メソッドは、double型に「Approximately」と「Round」という二つのメソッドを追加しています。
正確に言えば追加している「ように見える」だけで、MathExtensionクラスの静的メソッドをあたかもdouble型のインスタンスメソッドのように呼び出せるようになっただけです。

拡張メソッドの書式


using System;

namespace SampleNamespace
{
    static class クラス名
    {
        public static 戻り値の型 拡張メソッド名(this データ型 引数名...)
        {
            //処理
        }
    }
}

拡張メソッドを作るためにはまず静的クラス(staticクラス)を定義します。
拡張メソッド用の静的クラスは他のクラスの内部クラスとしては定義できません。
つまりMainメソッドがある「Program」クラス内では定義できないので、その外側に定義します。
クラス名は自由です。

静的クラス内では通常通りメソッドを定義するのですが、第一引数は必ずthisキーワードを付けた引数を指定します。
この引数のデータ型に新しいメソッドが追加されます。

「静的クラスかつ第一引数にthisを指定したメソッド」が拡張メソッドとなります
拡張メソッドは第一引数に指定したのデータ型のインスタンスメソッドとして呼び出せます。


double real = 1234.567;
Console.WriteLine(real.Round(1));
1234.6

拡張メソッド側では、このインスタンス(変数)の値が第一引数に渡されることになります。
拡張メソッドに引数がある場合はひとつずらして第二引数以降に渡されることになります。

拡張メソッドは通常の静的メソッドとしても呼び出すことができます。


double real = 1234.567;
Console.WriteLine(MathExtension.Round(real, 1));

拡張メソッドの用途

拡張メソッドは静的クラスの静的メソッドを別の形式で呼び出せるようにするものです。
特に使用しなくても困ることはありませんが、メソッドチェーンに組み込めるのは通常の静的メソッドにはない利点です。


double real = 1234.567;

bool b1 = real.Round().Approximately(0.1);

//↑を静的メソッドで呼び出す場合
bool b2 = MathExtension.Approximately(
    MathExtension.Round(real), 0.1);

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

拡張メソッドはインターフェイスに対しても使用できます。
ジェネリックメソッドを利用した拡張メソッドにすることもできます。


public static class IEnumerableExtention
{
    //要素をstring型に変換して配列で返す
    public static string[] ToStringArray<T>(this IEnumerable<T> sequence)
    {
        string[] ret = new string[sequence.Count()];
        int count = 0;
        foreach (var s in sequence)
            ret[count++] = s.ToString();

        return ret;
    }
}

int[] nums = new int[] { 1, 2, 3 };
string[] strs = nums.ToStringArray();

foreach (string s in strs)
    Console.WriteLine(s);
1
2
3

インターフェイスに対して拡張メソッドを定義すると、そのインターフェイスを継承するクラスすべてで拡張メソッドが使用できるようになります。
LINQのメソッド群はIEnumerableインターフェイスの拡張メソッドの形式で提供されています。