拡張メソッドは以下の場合にのみ利用すべきである。
- インターフェイスにデフォルト実装を追加する
- 閉じたジェネリック型に振る舞いを追加する
- 合成可能なインターフェイスを作成する
拡張メソッドは、型に機能を付け足すが、型の振る舞いを変更するわけではない。
拡張メソッドは以下のような用途には使ってはならない。
- クラスにデフォルト実装を追加する
- 複数のクラスに拡張メソッドを作成し、名前空間によって切り換え可能にする
拡張メソッドの使いすぎや誤用は、メソッドの衝突を引き起こし、コードのメンテナンスコストを増大させる。
たとえば、以下のようなPerson
クラスがあるとする。
public sealed class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
このクラスはsealed
なので、Person
に機能を付け足すことはできない。Person
の情報を、一定のフォーマットに従って出力したい場合、どのように実装すればいいだろうか?
以下のような拡張メソッドの作成は望ましくない方法である。
// 望ましくない方法
namespace ConsoleExtensions
{
public static class ConsoleReport
{
public static string Format(this Person target) =>
$"{target.LastName,20}, {target.FirstName,15}";
}
}
もし、レポート方法をXMLに切り換えたくなったら、XmlExtensions.XmlReport.Format()
を作成し、using
文によって使用する拡張メソッドを切り換えるという方法がある。しかし、この方法では、誤った名前空間のインポートによるバグの発生や、両方の拡張メソッドを同時に使用できない等の問題が発生する。
このような機能は、以下のように、独立したクラスの静的メソッドとして実装すべきである。
public static class PersonReports
{
public static string FormatAsText(Person target) =>
$"{target.LastName,20}, {target.FirstName,15}";
public static string FormatAsXml(Person target) =>
new XElement(nameof(Person),
new XElement(nameof(Person.LastName), target.LastName),
new XElement(nameof(Person.FirstName), target.FirstName)
).ToString();
}
拡張メソッドは、特定の型に対して、単一かつグローバルなものとみなすべきである。名前空間によるオーバーロードは決して行ってはならない。同一のシグネチャで複数の拡張メソッドを作る必要が出てきた場合は、設計を見直し、静的メソッドに変更すべきである。