Kokudoriing

技術系与太話ブログ

拡張メソッドの「拡張」ってなにさ

拡張メソッドはC#3.0から主にLINQをサポートするために追加された機能。
もちろんLINQ以外にもいろんな用途に使えます。

そもそも拡張メソッドの「拡張」とは何か。
安直に考えれば既存の型に機能を拡張するというもの。
では何故拡張するのか。つまりバージョニングの問題に対する解決策が拡張メソッドなのか。

つまり、初期バージョンといてライブラリやらでパブリックな型を公開するときには拡張メソッドの出番はないのか。

拡張メソッドの「拡張」は機能を拡張するもの。
もう少し掘り下げていくと、コンテキストを拡張するもの。

例えばこんな例。

using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
	class Program
	{
		static void Main(string[] args)
		{
			var task = new Task<string>(() =>"Hello");
			task.ToObservable()
				.Select(str => str + " C#!")
				.Subscribe(str => Console.WriteLine(str));
			task.Start();
			Console.Read();
		}
	}
}

作為的なコードなのであまり意味無いですし、Rx 使ってるのでNugetでRx入れないと動かないというあまり良い例ではないのですが・・。
ここで重要なのは、ToObservable は Task, Task の拡張メソッドだということ。

じゃあ例えば Rx と Task が同時期に標準ライブラリに入っていた場合、
BCL 設計チームは ToObservable を Task, Task のメソッドとしていたのか?
恐らくしなかったでしょう。

Task, Task のメソッドに IObservable を返す ToObservable を入れると、Task は IObservable に依存することになります。
もちろん Task と IObservable は相互に関係し合えど独立な存在です。

ToObservable は以前なら TaskReactiveHelper クラスの FromTask とかいう静的メソッドにでもあるべきメソッドです。
しかし task.ToObservable と書けた方がよりオブジェクト指向として適切でしょう。

同じ関係は LINQ to Object を実現するための IEnumerable の拡張メソッド群にも言えます。
IEnumerable は「列挙可能」という保証しか行いませんし行うべきではありません。
列挙可能なモノが Take で任意の数だけ補足出来たり、Where で任意の条件でフィルタリング出来たりすることは列挙可能という保証と直接関係しません。
つまり、列挙可能ならば Take, Where などは使えるが、列挙可能である条件として Take, Where が使えることは関係しないということ。

また、ToList やら ToDictionary やらは List や Dictionary などの
IEnumerable から見ると依存していない型をシグニチャに含んでしまっています。
List が IEnumerable に依存することは(List が IEnumerable を実装しているので)問題ありませんが、逆だと問題です。

さて、ここで ToObservable は System.Reactive.Threading.Tasks 名前空間に、
Take, Where 等の LINQ は System.Linq 名前空間に存在します。
これは「.NETのクラスライブラリ設計」でも推奨されている構造で、
別の型に依存するような拡張メソッドは名前空間は本筋の名前空間とは異なる名前空間上で記述されるべきです。

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

つまり、拡張メソッドは「その型としての機能」から拡張した「別の型を依存するような機能」を
あたかも「その型としての機能」として使えるように見せかける機能です。

これは大きな力なので、大いなる力には大いなる責任が伴います
「その型の機能」とは「その型のコンテキスト」なので、コンテキストの拡張はコンテキストの破壊を意味します。
これは、単一責任原則を違反する危険性を生みます。

あくまでも型は抽象にのみ依存し、複数の責任を持つべきでありません。
元々は別クラスのヘルパメソッドであるべきだったメソッドを、
あたかもその型のメソッドのように見せかけているということをよく理解しておかなければなりません。


さて、C#5.0 が出て世間は async/await だー、Caller Info だーと叫んでいる中今更の拡張メソッドのお話しでした。
そういえば例に上げた Rx も非同期関係なので async/await と縁があります。
縁がありますというか Rx2.0 では IObservable を返すメソッドが awaitable なのでモロ関係したり。

そのうち Rx, async/await のお話しも出来れば良いのですが、
スレッドセーフとかが絡むとわけわかんなくなるほど不勉強なのでたらんとお待ち頂ければ幸いです。