はじめに
C#のインターフェイスで様々なバリエーションを定義したい場合に拡張メソッドを用いてバリエーションの定義をすると利点が多いため、そのガイドラインを記します。
インターフェースと拡張メソッドを用いたバリエーション
- インターフェースには、プリミティブなメソッドのみを定義し、そのバリエーションは拡張メソッドとして定義すると良い。
- 利点
- インターフェース実装者は最低限のインターフェースのみ実装すれば良い。
- 単体テストでもバリエーション部分のメソッドのモック実装が不要となる。
- さらにメソッドを増やす場合も、インターフェースが変わらないのでインターフェース実装をしている他ユーザへの影響がない。
- フレームワーク、ライブラリ提供側は、インターフェースはプリミティブ化して、バリエーションを拡張メソッド化するアプローチを取ると良い。
公式の拡張メソッドのガイドライン
上記はマイクロソフトの拡張メソッドのガイドラインです。
どのような場合に拡張メソッドを利用すべきかが記されています。以下に例を記します。
拡張メソッドをむやみに定義することは避けてください。これは自分が所有していない型に対して特に当てはまります。
型のソース コードを所有している場合は、代わりに通常のインスタンス メソッドを使用することを検討してください。 所有していないときにメソッドを追加する場合は、細心の注意を払ってください。 拡張メソッドを自由に使用すると、それらのメソッドを使用するように設計されていなかった種類の API が乱雑になる可能性があります。
Loggerでの例
Microsof.Extensions.Loggingで同様のアプローチを取っています。
// インターフェイスでプリミティブなログのメソッドを定義する
public interface ILogger
{
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
bool IsEnabled(LogLevel logLevel);
IDisposable BeginScope<TState>(TState state);
}
// ログのバリエーションを拡張メソッドで定義する
public static class LoggerExtensions
{
public static void LogDebug(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args)
{
logger.Log(LogLevel.Debug, eventId, exception, message, args);
}
...
public static void LogError(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args)
{
logger.Log(LogLevel.Error, eventId, exception, message, args);
}
...
}
Configurationでの例
// インターフェイスでプリミティブなConfigurationのメソッドを定義する
public interface IConfiguration
{
IConfigurationSection GetSection(string key);
IEnumerable<IConfigurationSection> GetChildren();
IChangeToken GetReloadToken();
}
// Configurationのバリエーションを拡張メソッドで定義する
public static class ConfigurationExtensions
{
public static bool Exists(this IConfigurationSection section)
{
if (section == null)
{
return false;
}
return section.Value != null || section.GetChildren().Any();
}
public static string GetConnectionString(this IConfiguration configuration, string name)
{
return configuration?.GetSection("ConnectionStrings")?[name];
}
...
}